Git Product home page Git Product logo

meltysynth's Introduction

MeltySynth

MeltySynth is a SoundFont synthesizer written in C#. The purpose of this project is to provide a MIDI music playback functionality for any .NET applications without complicated dependencies. The codebase is lightweight and can be applied to any audio drivers which support streaming audio, such as SFML.Net, Silk.NET, OpenTK, and NAudio.

The entire code is heavily inspired by the following projects:

An example code to synthesize a simple chord:

// Create the synthesizer.
var sampleRate = 44100;
var synthesizer = new Synthesizer("TimGM6mb.sf2", sampleRate);

// Play some notes (middle C, E, G).
synthesizer.NoteOn(0, 60, 100);
synthesizer.NoteOn(0, 64, 100);
synthesizer.NoteOn(0, 67, 100);

// The output buffer (3 seconds).
var left = new float[3 * sampleRate];
var right = new float[3 * sampleRate];

// Render the waveform.
synthesizer.Render(left, right);

Another example code to synthesize a MIDI file:

// Create the synthesizer.
var sampleRate = 44100;
var synthesizer = new Synthesizer("TimGM6mb.sf2", sampleRate);

// Read the MIDI file.
var midiFile = new MidiFile("flourish.mid");
var sequencer = new MidiFileSequencer(synthesizer);
sequencer.Play(midiFile, false);

// The output buffer.
var left = new float[(int)(sampleRate * midiFile.Length.TotalSeconds)];
var right = new float[(int)(sampleRate * midiFile.Length.TotalSeconds)];

// Render the waveform.
sequencer.Render(left, right);

Features

  • Suitable for both real-time and offline synthesis.
  • Support for standard MIDI files with additional functionalities like dynamic tempo change.
  • No dependencies other than .NET Standard 2.1.

Installation

The NuGet package is available:

Install-Package MeltySynth

All the classes are in the MeltySynth namespace:

using MeltySynth;

If you don't like DLLs, copy all the .cs files to your project.

Playing sound

MeltySynth can only generate PCM waveforms; MeltySynth itself does not have the ability to play sound from speakers. To make the sound audible, export the generated waveform as an audio file (e.g., WAV file) or pass it to some audio driver (e.g., NAudio). If you are not very familiar with how to handle PCM audio, NAudio's tutorials should be helpful.

Note that MeltySynth does not provide thread safety. If you want to send notes and render the waveform in separate threads, you must ensure that the related methods will not be called simultaneously.

Demo

A demo song generated with Arachno SoundFont

https://www.youtube.com/watch?v=xNgsIJKxPkI

Youtube video

A Doom port written in C# with MIDI music playback

https://www.youtube.com/watch?v=_j1izHgIT4U

Youtube video

A virtual keyboard made with Raylib-CsLo

https://www.youtube.com/watch?v=a8vuIq4JKhs

Youtube video

Use MeltySynth as a MIDI device (NAudio + loopMIDI)

https://www.youtube.com/watch?v=BiFxvzs0jUI

Youtube video

Use MeltySynth as a VST plugin (VST.NET)

https://www.youtube.com/watch?v=IUKIEWvw6Ik

Youtube video

Examples

MIDI file player for various audio drivers

Handling SoundFont

To enumerate samples in the SoundFont:

var soundFont = new SoundFont("TimGM6mb.sf2");

foreach (var sample in soundFont.SampleHeaders)
{
    Console.WriteLine(sample.Name);
}

To enumerate instruments in the SoundFont:

var soundFont = new SoundFont("TimGM6mb.sf2");

foreach (var instrument in soundFont.Instruments)
{
    Console.WriteLine(instrument.Name);
}

To enumerate presets in the SoundFont:

var soundFont = new SoundFont("TimGM6mb.sf2");

foreach (var preset in soundFont.Presets)
{
    var bankNumber = preset.BankNumber.ToString("000");
    var patchNumber = preset.PatchNumber.ToString("000");

    Console.WriteLine($"{bankNumber}:{patchNumber} {preset.Name}");
}

Handling synthesizer

To change the instrument to play, send a program change command (0xC0) to the synthesizer:

// Create the synthesizer.
var sampleRate = 44100;
var synthesizer = new Synthesizer("TimGM6mb.sf2", sampleRate);

// Change the instrument to electric guitar (#30).
synthesizer.ProcessMidiMessage(0, 0xC0, 30, 0);

// Play some notes (middle C, E, G).
synthesizer.NoteOn(0, 60, 100);
synthesizer.NoteOn(0, 64, 100);
synthesizer.NoteOn(0, 67, 100);

// The output buffer (3 seconds).
var left = new float[3 * sampleRate];
var right = new float[3 * sampleRate];

// Render the waveform.
synthesizer.Render(left, right);

To play a melody, render the sound as a sequence of short blocks:

// Create the synthesizer.
var sampleRate = 44100;
var synthesizer = new Synthesizer("TimGM6mb.sf2", sampleRate);

// The length of a block is 0.1 sec.
var blockSize = sampleRate / 10;

// The entire output is 3 sec.
var blockCount = 30;

// Define the melody.
// A single row indicates the start timing, end timing, and pitch.
var data = new int[][]
{
    new int[] {  5, 10, 60 },
    new int[] { 10, 15, 64 },
    new int[] { 15, 25, 67 }
};

// The output buffer.
var left = new float[blockSize * blockCount];
var right = new float[blockSize * blockCount];

for (var t = 0; t < blockCount; t++)
{
    // Process the melody.
    foreach (var row in data)
    {
        if (t == row[0]) synthesizer.NoteOn(0, row[2], 100);
        if (t == row[1]) synthesizer.NoteOff(0, row[2]);
    }

    // Render the block.
    var blockLeft = left.AsSpan(blockSize * t, blockSize);
    var blockRight = right.AsSpan(blockSize * t, blockSize);
    synthesizer.Render(blockLeft, blockRight);
}

Handling MIDI file sequencer

To change the playback speed:

// Create the synthesizer.
var sampleRate = 44100;
var synthesizer = new Synthesizer("TimGM6mb.sf2", sampleRate);

// Read the MIDI file.
var midiFile = new MidiFile(@"C:\Windows\Media\flourish.mid");
var sequencer = new MidiFileSequencer(synthesizer);

// Play the MIDI file.
sequencer.Play(midiFile, false);

// Change the playback speed.
sequencer.Speed = 1.5F;

// The output buffer.
var left = new float[(int)(sampleRate * midiFile.Length.TotalSeconds / sequencer.Speed)];
var right = new float[(int)(sampleRate * midiFile.Length.TotalSeconds / sequencer.Speed)];

// Render the waveform.
sequencer.Render(left, right);

To mute a certain track:

// Create the synthesizer.
var sampleRate = 44100;
var synthesizer = new Synthesizer("TimGM6mb.sf2", sampleRate);

// Read the MIDI file.
var midiFile = new MidiFile(@"C:\Windows\Media\flourish.mid");
var sequencer = new MidiFileSequencer(synthesizer);

// Discard MIDI messages if its channel is the percussion channel.
sequencer.OnSendMessage = (synthesizer, channel, command, data1, data2) =>
{
    if (channel == 9)
    {
        return;
    }

    synthesizer.ProcessMidiMessage(channel, command, data1, data2);
};

// Play the MIDI file.
sequencer.Play(midiFile, false);

// The output buffer.
var left = new float[(int)(sampleRate * midiFile.Length.TotalSeconds)];
var right = new float[(int)(sampleRate * midiFile.Length.TotalSeconds)];

// Render the waveform.
sequencer.Render(left, right);

To change the instruments used in the MIDI file:

// Create the synthesizer.
var sampleRate = 44100;
var synthesizer = new Synthesizer("TimGM6mb.sf2", sampleRate);

// Read the MIDI file.
var midiFile = new MidiFile(@"C:\Windows\Media\flourish.mid");
var sequencer = new MidiFileSequencer(synthesizer);

// Turn all the instruments into electric guitars.
sequencer.OnSendMessage = (synthesizer, channel, command, data1, data2) =>
{
    if (command == 0xC0)
    {
        data1 = 30;
    }

    synthesizer.ProcessMidiMessage(channel, command, data1, data2);
};

// Play the MIDI file.
sequencer.Play(midiFile, false);

// The output buffer.
var left = new float[(int)(sampleRate * midiFile.Length.TotalSeconds)];
var right = new float[(int)(sampleRate * midiFile.Length.TotalSeconds)];

// Render the waveform.
sequencer.Render(left, right);

Todo

  • Wave synthesis
    • SoundFont reader
    • Waveform generator
    • Envelope generator
    • Low-pass filter
    • Vibrato LFO
    • Modulation LFO
  • MIDI message processing
    • Note on/off
    • Bank selection
    • Modulation
    • Volume control
    • Pan
    • Expression
    • Hold pedal
    • Program change
    • Pitch bend
    • Tuning
  • Effects
    • Reverb
    • Chorus
  • Other things
    • Standard MIDI file support
    • Loop extension support
    • Performace optimization

License

MeltySynth is available under the MIT license.

References

meltysynth's People

Contributors

sinshu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

meltysynth's Issues

Possible typo?

I noticed that in Synthesizer.ProcessMidiMessage(), the controller messages for the modulation are both set to call SetModulationCoase(). But on line 190, should that be SetModulationFine() instead of SetModulationCoarse()?

case 0x01: // Modulation Coarse
channelInfo.SetModulationCoarse(data2);
break;
case 0x21: // Modulation Fine
channelInfo.SetModulationCoarse(data2);
break;

ArrayMath.MultiplyAdd on Unity Android

Hi and thanks for making this great tool!

I'm using meltysynth to do some real-time rendering of audio in Unity, through the OnAudioFilterRead callback function.

When I used the IL2CPP to compile the project for android and ran it on multiple devices, the audio was stuttering horribly.

Upon running some profiling sessions I found that almost all of the CPU load was actually from the Garbage Collector, specifically in this function:

        public static void MultiplyAdd(float a, float[] x, float[] destination)
        {
            var vx = MemoryMarshal.Cast<float, Vector<float>>(x);
            var vd = MemoryMarshal.Cast<float, Vector<float>>(destination);
        
            var count = 0;
        
            for (var i = 0; i < vd.Length; i++)
            {
                vd[i] += a * vx[i];
                count += Vector<float>.Count;
            }
        
            for (var i = count; i < destination.Length; i++)
            {
                destination[i] += a * x[i];
            }
        }

Perhaps this is premature optimization, since changing the code to:

        public static void MultiplyAdd(float a, float[] x, float[] destination)
        {
            for (int i = 0; i < x.Length; i++)
            {
                destination[i] += a * x[i];
            }
        }

seems to have eliminated the load from this function entirely.

Perhaps this is not relevant to your intended use case, but I'd still like to let you know.

Possible .dls soundfont Support?

Hello,

First of all, thank you for making this library! It's been such a huge help in my project.

I was wondering, is there room for adding .dls support? I used AWave Studio to convert .dls -> .sf2, but the results are not 100% accurate: I suspect that the volume envelopes (ADSR) and tuning/pitch output might be slightly different from the source material.

You can find my project here, for reference: https://github.com/mysterypaint/OpenLaMulana

Thank you for your time!

  • mysterypaint

Question: How to play notes with timing?

The example plays three note at the same time, from start to end, sort of like this.

image

But what if I want the start time not to be at the beginning and the end time not to be the end? Something like the followings?

image

The NoteOn method do not seem to have any timing parameter, and the other method is NoteOff, but I don't get how to use these to play notes with timing.

[Question] Arachno SoundFont Drum Kits

Hi, thanks a lot for this amazing library! It's awesome👍, and I've used it in my personal project here (Fluent Synth)😆.
In general it is very straightforward and works great out of the box, but I do notice a difference in playback compared to VLC's FluidSynth. At the moment I am still looking into whether it has to do with my setup or it's just the two platforms interpret MIDI files different.

Anyway, my question here is related to Arachno SoundFont - Version 1.0: in the documentation of this sound font it mentioned there are some "drum kits" available:

image

I wonder how we can use those drum kits through Melty Synth? (In the documentation it mentioned General MIDI has Music Box or instrument number 10 used as default for drum kit, but not further information on that)
If you have any information on how those drum kits are defined inside a MIDI file that would also be very helpful!

"MuseScore GM High Quality" soundfont Piano presets all Very Quiet

I will try to come back to this issue with more details later, but the short version is, as in the title, that the Piano presets in the MuseScore fonts are very quiet, but everything else seems normal.

I am running it in Unity and made a few changes, but again all the other presets sound fine, so there is something that they are doing with their piano presets which is triggering some bug in the way MeltySynth is working in Unity.

The soundfornt is an SF3 that was converted via polyphone to sf2, so it could be a Polyphone issue.

Sorry for the rushed issue, I'll be sure to come back and improve it within the week with a workaround if I find it.

Thanks for making this! It's really great!

[Discussion] add a synth.selectPreset function?

In my README, I have these rather wordy docs.

You might want to select the patchNumber & bankNumber
of the instrument you want to play, if it's not 0.

Look through synth.soundFont.presets[X].patchNumber & bankNumber
for all valid instrument choices.

Example for setting patchNumer:

  int patchNumber = synth.soundFont.presets[0].patchNumber 
  synth.processMidiMessage(
    channel:0, 
    command:0xC0, // 0xC0 = program changes
    data1:patchNumber, 
    data2:0
  );

Example for setting bankNumber:

  int bankNumber = synth.soundFont.presets[0].bankNumber 
  synth.processMidiMessage(
    channel:0, 
    command:0xB0, // 0xB0 = control change
    data1:0x00,   // 0x00 = bank select
    data2:bankNumber,
  );

So simplify things, perhaps we could add a synth. selectPreset(int channel, int preset) function?

void selectPreset(int channel, int preset)
{
    if (preset >= synth.soundFont.presets.length) {
        throw InvalidArgument('selectInstrumentPreset: invalid preset $preset >= ${synth.soundFont.presets.length}');
    }

    this.processMidiMessage(
        channel: channel, 
        command: 0xC0, // program change
        data1: this.soundFont.presets[preset].patchNumber, 
        data2: 0
    );

    this.processMidiMessage(
        channel: channel, 
        command: 0xB0, // control change
        data1: 0x00,   // bank select
        data2: this.soundFont.presets[preset].bankNumber,
    );
}

Curious your thoughts @sinshu

Generate specific sample range and change position

I have a Unity game and want to play user provided MIDI files on the fly.

Therefor, I want to synthesize samples when needed, not all at once.
For example, my code may look similar to this:

private void CreateAudioClip()
{
    audioSource.clip = AudioClip.Create("MIDI {fileName}", audioClipLengthInSamples, midiSynthesizerChannelCount, audioClipSampleRate, true, OnAudioClipRead, OnAudioClipSetPosition);
}

private void OnAudioClipRead(float[] data)
{
    // This method is called when Unity plays the audio.
    // It asks for samples from `audioClip.timeSamples` to `audioClip.timeSamples + data.Length`
    FillOutputBuffer(data, midiSynthesizerChannelCount);
}

private void OnAudioClipSetPosition(int positionInSamples)
{
    // This method is called when Unity changes the playback position.
    // It sets audioClip.timeSamples
    sequencer.SeekSampleTime(positionInSamples);
}

I found the Render method of MeltySynth to be good.
However, I did not find a way to change the playback position. Is this missing or am I looking in the wrong place?

So far, I use this other lib to play MIDI in Unity, but it has issues with some MIDI files.

Thanks for the help!

Add InstrumentChange constant

In alternative to #25, we can add single constant InstrumentChange inside Synthesizer?

Sample:

Before

// Change the instrument to electric guitar (#30).
synthesizer.ProcessMidiMessage(0, 0xC0, 30, 0);

After

// Change the instrument to electric guitar (#30).
synthesizer.ProcessMidiMessage(0, Synthesizer.InstrumentChange, 30, 0);

Sample in sound font with start loop = 0 and end loop = 0

This is a great library! Thanks for your excellent work.

I have a sound font file in which start loop and end loop values for all samples is zero.

p1.zip

This file raises an exception in CheckSamples() and CheckRegions() functions in SoundFont.cs.

I don't know if having both values as zero is permitted or not but another sound font library I use does not complain about this issue.

I fixed this problem by changing the condition from < to <= in the following parts of the code:

                if (!(0 <= sample.EndLoop && sample.EndLoop <= sampleCount))
                {
                    throw new InvalidDataException($"The loop end position of the sample '{sample.Name}' is out of range.");
                }

                if (!(0 <= region.SampleEndLoop && region.SampleEndLoop <= sampleCount))
                {
                    throw new InvalidDataException($"The loop end position of the sample '{region.Sample.Name}' in the instrument '{instrument.Name}' is out of range.");
                }

Can you also update it if having both values as zero does not effect the synthesize process?

Unable to parse soundfont banks greater than 2 GB

Hi and congratulations on this amazing project!

I have encountered an issue when reading a packed SF file larger than 2GB. Upon inspecting the code, I noticed that the size is read as a signed Int32. This could lead to an overflow if the size is greater than 2^32 / 2 - 1.

Changing this line:

var size = reader.ReadInt32();

to ReadUInt32 might resolve the overflow issue, but it results in a subsequent error soon after at MemoryMarshal.Cast because Span<TFrom> has an int length and cannot exceed 2,147,483,647 bytes.

In any case, storing 2+ GB of wave samples in RAM as an array of short[] Samples might not be a wise choice IMHO :)
My general advice would be to use a memory-mapped file. In short, this approach allows you to read a file as a contiguous area of virtual memory, dynamically swapped by the OS from disk to a physical page of RAM.

Here's a quick sample code for reference:

unsafe
{
    using var file = MemoryMappedFile.CreateFromFile("path");
    using var view = file.CreateViewAccessor();

    byte* ptr = null;
    view.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
    try
    {
        var span = new ReadOnlySpan<short>(ptr + offset, length);

        // Do things with the span

        for (int i = 0; i < span.Length; i++)
            Console.WriteLine(span[i]);
    }
    finally
    {
        if (ptr != null)
            view.SafeMemoryMappedViewHandle.ReleasePointer();
    }
}

Unable to tell when a midi has finished

When a file is not looping, Is there a way to tell when the media has finished playing? I've been looking at the examples, and there appears to be no difference in output in the Render function when the midi has finished playing, is there a way I can check that a midi has finished playing?

Disable reverb?

Hi, I'm using this to batch convert sequenced Nintendo DS music, and I need it to be as accurate as possible. The DS doesn't really have many effects, so I'd like to minimize the reverb and other effects that are applied when rendering. Is there any way to disable reverb and other effects?

Suggestions for sequencer API changes

Hello,

right now the MidiFileSequencer object can only play and stop. Also the Render() method gives no indication of how many non-zero samples were written before reaching the end of the file. I would be interested in the following:

  • a Length() or Duration() method that indicates the length of the file
  • a Tell() or Pos() method that indicates the current position in the file
  • a Seek() method to seek into the file (block granularity would be OK)

To avoid accuracy issues, I would also suggest that these methods handle a number of samples rather than a duration, so that the client application can directly use the data with Render().

Also note that seq.LoopIndex is not updated (and is currently unused).

What do you think? All these changes are fairly easy and I will gladly submit a PR, but I’d like to get your opinion first.

Big sound font files

I have some sound font files which are hundreds of megabytes or even a gigabyte in size.

When a sound font is loaded, all the data in it is read into memory.

This is generally not a problem on a PC, but on a mobile phone such sound font files create a memory issue. (I am also using the dart version of this library (DartMeltySoundFont) in an Android application)

Assuming that the external storage is fast enough for lazy loading and processing, is it possible to load samples whenever they are needed into a predefined maximum memory space?

Thanks for making this!!

I made a Dart port of MeltySynth =) Just wanted to let you know of its existence!

https://github.com/chipweinberger/DartMeltySoundFont

I looked around for days for a good SoundFont implementation to port to Dart, and yours is incredibly clean and well written. Really impressive! I looked in depth to at least 4 different implementations!

Really happy I found your implementation, and for all your work making this high quality library!

Parametric chorus and reverb

Chorus and reverb effects are applied with predefined parameter values.

Can it be possible to set the parameters of these effects in SynthesizerSettings?

Sound cracks when sustain (damper) pedal is on

Hi,
My midi keyboard application I created with this library runs perfectly.

However sound cracks happen when I play multiple (many many) notes at the same time with the sustain pedal on.

Here is a sample video which describes what I mean:

x.mp4

I initialize the synthesizer like this:

                var format = WaveFormat.CreateIeeeFloatWaveFormat(44100, 2);
                var settings = new SynthesizerSettings(format.SampleRate);
                settings.EnableReverbAndChorus = true;
                synthesizer = new Synthesizer(soundFontPath, settings);
                synthesizer.MasterVolume = 1.5F;

And I use the following ISampleProvider with the NAudio library:

using System;
using NAudio.Wave;
using MeltySynth;

public class MidiSampleProvider : ISampleProvider
{
    private static WaveFormat format = WaveFormat.CreateIeeeFloatWaveFormat(44100, 2);
    private Synthesizer _synthesizer;

    public MidiSampleProvider(Synthesizer synthesizer)
    {
        _synthesizer = synthesizer;
    }

    public int Read(float[] buffer, int offset, int count)
    {
        _synthesizer.RenderInterleaved(buffer.AsSpan(offset, count));
        return count;
    }
    public WaveFormat WaveFormat => format;
}

I have the exact same problem with the Dart version of this library on Android.

Can you please advice what can be done?

Provide example showing how to play notes with NAudio

Hi,

Sorry, my request might sound like a very stupid one, and maybe this is not the correct place to ask, but I'm struggling to actually play the waveform generated by Meltysynth.

What I'm trying to do:

  • generate a midi file in memory
  • render it with Meltysynth into a memory stream
  • finally play the stream

What I've achieved so far:

private static void PlayMeltysynthChord()
		{
			var sampleRate = 44100;
			var synthesizer = new Synthesizer(@"..\Resources\Raw\Abbey-Steinway-D-v1.9.sf2", sampleRate);

			synthesizer.NoteOn(0, 60, 100);
			synthesizer.NoteOn(0, 63, 100);
			synthesizer.NoteOn(0, 66, 100);

			var mono = new float[3 * sampleRate];
			synthesizer.RenderMono(mono);

			var byteArray = new byte[mono.Length * 4];
			Buffer.BlockCopy(mono, 0, byteArray, 0, byteArray.Length); 

			var wav = new WavePcmFormat(byteArray, numChannels: 1, sampleRate: (uint)sampleRate, bitsPerSample: 16);
			var rawDataWithHeader = wav.ToBytesArray();

			var stream = new MemoryStream(rawDataWithHeader);

			var player = new System.Media.SoundPlayer(stream);
			player.Play();
		}

The 'WavePCMFormat' method inserts the .wav header to the data. The result I get is a few seconds of white noise, so I assume there is something wrong in the transformation of the rendered PCM to the .wav stream.

Any help is welcome... Thanks in advance

Question: How to select an instrument to play the notes with?

I tried the chord example, and it sounded like a piano. What if I want to play some other instrument in the sound font? How to change the instrument? I even looked at the source code for NoteOn but I could not figure it out. Is it hardcoded to be a piano or the first instrument?

// Play some notes (middle C, E, G).
synthesizer.NoteOn(0, 60, 100);
synthesizer.NoteOn(0, 64, 100);
synthesizer.NoteOn(0, 67, 100);

Using NAudio with MeltySynth

Hii ! I currently have a problem using your NAudio example. I'm using .NET 6.0, and when using NAudio.Wave, there is no WaveOut function.
I'd like to play notes in real-time from a frequency (that I can convert to a note), and use a soundfont to play the note. Is this possible ? Is there any workaround ? Could you provide an example if possible ? I can't figure out how I can play the notes...

GM2 Patch Change Overrides

Many thanks for this wonderful product.

I am currently using it to render midi files to audio in a variety of ways.

I am stuck however, trying to override GM patch changes in the midi sequences with GM2 patch changes. I don't seem to be able to get the overrides to work.

I am using a SoundFont that claims to be GM2 compatible from https://musical-artifacts.com/artifacts/1346,
and have the following code in place ...

` ... other stuff ...

    public const int PATCH_CHANGE = 0xC0;
    public const int BANK_CHANGE = 0xB0;
    public const int BANK_0 = 0;
    public const int BANK_32 = 32;


    ... other stuff ...

            // Create the synthesizer.
            synthesizer = new Synthesizer(SoundFontFile, sampleRate);

            midiFile = new MidiFile(MidiFile);
            sequencer = new MidiFileSequencer(synthesizer);

            sequencer.OnSendMessage = (synthesizer, channel, command, data1, data2) =>
            {
                if (command == PATCH_CHANGE && channel >= 0 && channel <= 15 && PatchOverRide[channel] != NO_OVERRIDE)
                {  //  sequencer encountered a patch change for a channel that we want to override the patch for. Override the patch ...
                    synthesizer.ProcessMidiMessage(channel, BANK_CHANGE, BANK_0, PatchOverRide_Bank_0_MSB[channel]);   // issue the Bank 0 change
                    synthesizer.ProcessMidiMessage(channel, BANK_CHANGE, BANK_32, PatchOverRide_Bank_32_MSB[channel]); // issue the Bank 32 change
                    synthesizer.ProcessMidiMessage(channel, command, PatchOverRide[channel], data2);  //                  issue the revised patch change  
                }
                else
                {  //  We're not interested in this message. Let the sequencer process it "as is".
                    synthesizer.ProcessMidiMessage(channel, command, data1, data2);
                }
            };

            sequencer.Play(midiFile, false);

            // setup the output buffer.
            left = new float[(int)(sampleRate * midiFile.Length.TotalSeconds)];
            right = new float[(int)(sampleRate * midiFile.Length.TotalSeconds)];

            // Render the waveform.
            sequencer.Render(left, right);

    ... other stuff ...`

The PatchOverRide's are working, however the BANK_0, and BANK_32 bank changes have no effect. For instance

Patch Bank32 Bank0 Name
063 121 001 Synth Brass 1
063 121 002 Synth Brass 3
063 121 003 Analog Brass 1
063 121 004 Jump Brass 1

all result in the same instrument being selected (063 121 001 Synth Brass 1) - i.e. no difference in the sound.

Do you have any suggestions as to how I can overcome this issue?

Regards
Jim Bayne

A little fix for NoteOn function

Hi,
I have an sf2 file for which NoteOn function creates no sound:

p2.zip

I made a little change in NoteOn function to make the library select the first preset in the preset array if it can not find a preset using the preset id:

            Preset preset;
            if (!presetLookup.TryGetValue(presetId, out preset))
            {
                if (soundFont.PresetArray.Length > 0)
                {
                    preset = soundFont.PresetArray[0];
                }
                else return;
                
            }

Is this the ideal way of fixing this problem?

I cant figure out how to use this

so im making a framework and i added audio stream implimentation to it a while ago. I found this thing and decided to try it out but for some reason nothing played. Im using your Raylib_cs example, and it works fine but as soon as i try to use it with my library it just doesnt play any sound. i used the same soundfont and midi with your example and it works fine, but mine just doesnt work. this is the details im pretty sure you will need:

Where I set everything up and play the midi:

short[] buffer;
Synthesizer synthesizer = new Synthesizer("assets/soundfonts/Arachno SoundFont - Version 1.0.sf2", LnG.game.window.sampleRate);
MidiFileSequencer sequencer;
MidiFile midiFile = new MidiFile("assets/songs/Super Mario 64 - Koji Kondo - Bowser Confrontation (Jeff Daily).mid");
override public void Start()
{
    buffer = new short[2 * LnG.game.window.bufferSize];
    sequencer = new MidiFileSequencer(synthesizer);
    sequencer.Play(midiFile, true);
    base.Start();
}

Where I render from the sequencer:

public override void UpdateAudio()
{
    sequencer.RenderInterleavedInt16(buffer);
    base.UpdateAudio();
}

Where I call UpdateAudio:

if (IsAudioStreamProcessed(stream))
{
    LnG.curState.UpdateAudio();
    fixed (short* p = buffer) //used from the Raylib_cs example
    {
        UpdateAudioStream(stream, p, bufferSize);
    }
}

Remember that I adapted all of this code from the Raylib_cs example and it just doesnt work.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.