pstrjds
pstrjds

Reputation: 17428

Playing MidiEventCollection directly

Using the NAudio framework, I have written code like this to play some midi notes:

// Guts of a play note method which takes a cancellation token, a note
// a channel and a duration - CurrentVolume is a property of the class
// that plays the notes
midiOut.Send(MidiMessage.StartNote(note, CurrentVolume, channel).RawData);
try
{
    await Task.Delay(duration, cancellationToken);
}
finally
{
    midiOut.Send(MidiMessage.StopNote(note, CurrentVolume, channel).RawData);
}

And this is working okay, but every so often there is a little skip/delay in the rendering which I am assuming is coming from the Task.Delay not always being exact. What I would like to do is just generate a midi collection and send the whole collection to the midi out device, but I can't seem to find a way to do that. I have gotten as far as generating a collection and I know how to save that to a file - so if the solution is to make a file and then somehow send the file, this is also acceptable.

var collection = new MidiEventCollection(0, 120);
collection.AddEvent(new NoteOnEvent(0, 1, 64, 127, 15), 1);
collection.AddEvent(new NoteOnEvent(15, 1, 65, 127, 15), 1);
collection.AddEvent(new NoteOnEvent(30, 1, 66, 127, 15), 1);
collection.AddEvent(new NoteOnEvent(45, 1, 67, 127, 15), 1);
collection.AddEvent(new NoteOnEvent(60, 1, 68, 127, 15), 1);

collection.PrepareForExport();

Upvotes: 1

Views: 743

Answers (2)

Maxim
Maxim

Reputation: 2128

Using DryWetMIDI you can write this code:

using Melanchall.DryWetMidi.Common;
using Melanchall.DryWetMidi.Devices;
using Melanchall.DryWetMidi.Core;
using Melanchall.DryWetMidi.Interaction;

// ...

var eventsToPlay = new MidiEvent[]
{
    new NoteOnEvent((SevenBitNumber)100, SevenBitNumber.MaxValue) { Channel = (FourBitNumber)10 },
    new NoteOffEvent((SevenBitNumber)100, SevenBitNumber.MinValue) { Channel = (FourBitNumber)10 },
    // ...
};

using (var outputDevice = OutputDevice.GetByName("Microsoft GS Wavetable Synth"))
using (var playback = new Playback(eventsToPlay, TempoMap.Default, outputDevice))
{
    playback.Play();
}

Or if you just need to play notes on single channel, you can use Pattern:

using MusicTheory = Melanchall.DryWetMidi.MusicTheory;
using Melanchall.DryWetMidi.Composing;

// ...

var pattern = new PatternBuilder()
    .Note(MusicTheory.Octave.Get(3).ASharp, length: MusicalTimeSpan.Quarter)
    .Note(MusicTheory.Octave.Get(3).C, length: MusicalTimeSpan.Eighth)
    // ...
    .Build();

using (var outputDevice = OutputDevice.GetByName("Microsoft GS Wavetable Synth"))
{
    pattern.Play(TempoMap.Default, (FourBitNumber)10, outputDevice);
}

Note that Play will block calling thread until all MIDI data played. For non-blocking playback use Start method of the Playback class:

var playback = pattern.GetPlayback(TempoMap.Default, (FourBitNumber)10, outputDevice);
playback.Start();

You can read more about playing MIDI data on the Playback page of the library docs.

Upvotes: 0

Mark Heath
Mark Heath

Reputation: 49492

There is a Windows API that lets you emit batches of MIDI events (see midiStreamOut for example) which would be ideal for this scenario but unfortunately NAudio does not contain wrappers for this. NAudio's MIDI capabilities are more focused on reading and writing MIDI files. Your options are either to create the p/invoke wrappers for the MIDI APIs I mentioned or to try a different audio library such as MIDI.NET

Upvotes: 1

Related Questions