It’s been a while.
So kicking off from where we left off this instalment is going to be looking at MIDI and how we can interoperate control characters sent through the
http://opensebj.blogspot.com/2009/02/introduction-to-using-naudio.html
http://opensebj.blogspot.com/2009/02/naudio-tutorial-2-mixing-multiple-wave.html
http://opensebj.blogspot.com/2009/03/naudio-tutorial-3-sample-properties.html
http://opensebj.blogspot.com/2009/03/naudio-tutorials-minor-note.html
http://opensebj.blogspot.com/2009/03/naudio-tutorial-4-sample-reversing.html
http://opensebj.blogspot.com/2009/04/naudio-tutorial-5-recording-audio.html
As you may by now expect, NAudio has a set of functions for this as well. You will find the useful set of functions under NAudio.Midi;
Setting It
Lets create a class to encapsulate the bulk of the
using NAudio.Midi;
namespace AudioInterface
{
public class NAudioMIDI
{
public MidiIn midiIn;
private bool monitoring;
private int midiInDevice;
Our midiInDevice represents what MIDI device on the system we want to use for this interface; in case you have more than one
Once we have defined what
/// <summary>
/// Get a list of MIDI Devices
/// </summary>
/// <returns>string[] of MIDI Device Names</returns>
public string[] GetMIDIInDevices()
{
// Get a list of devices
string[] returnDevices = new string[MidiIn.NumberOfDevices];
// Get the product name for each device found
for (int device = 0; device < MidiIn.NumberOfDevices; device++)
{
returnDevices[device] = MidiIn.DeviceInfo(device).ProductName;
}
return returnDevices;
}
Assuming that we want to allow the user to select a Device from a list of Device’s then we would pass this list back to a control which will populate this list with the available devices. With something like this from our Load method on the form class:
private void NAudioTutorial6_Load(object sender, EventArgs e)
{
<SNIP>
// Populate the devices available for the
string[] MIDIDevices = AudioInterface.NAudioInterface.nMIDI.GetMIDIInDevices();
foreach (string devices in MIDIDevices)
{
boxMIDIIn.Items.Add(devices);
}
try
{
boxMIDIIn.SelectedIndex = 0;
}catch(Exception except){
System.Windows.Forms.MessageBox.Show("No
}
<SNIP>
Starting It
Brilliant, so now we have a list of available
private void cmbMonitor_Click(object sender, EventArgs e)
{
// Setup the
AudioInterface.NAudioInterface.nMIDI.StartMonitoring(boxMIDIIn.SelectedIndex);
// Add the event handler, to handle the
AudioInterface.NAudioInterface.nMIDI.midiIn.MessageReceived += new EventHandler<MidiInMessageEventArgs>(midiIn_MessageReceived);
}
When StartMonitoring is called we through back to the nMIDI instance we created earlier and (using the selected MIDI device) setup the midiIn device and set the midiIn device to Start – which in turn, kicks NAudio in to gear to start monitoring MIDI messages received from the MIDI device.
public void StartMonitoring(int MIDIInDevice)
{
if (midiIn == null)
{
midiIn = new MidiIn(MIDIInDevice);
}
midiIn.Start();
monitoring = true;
}
Going back to cmbMonitor(…) we next setup the EventHandler for the
// Add the event handler, to handle the
AudioInterface.NAudioInterface.nMIDI.midiIn.MessageReceived += new EventHandler<MidiInMessageEventArgs>(midiIn_MessageReceived);
Playing It
For this to work, we need to have an event handler method setup to receive the messages, within the same class. From the line above you should see that the method is midiIn_MessageReceived - which we will have a look at now:
public void midiIn_MessageReceived(object sender, MidiInMessageEventArgs e)
{
// Exit if the MidiEvent is null or is the AutoSensing command code
if (e.MidiEvent != null && e.MidiEvent.CommandCode == MidiCommandCode.AutoSensing)
{
return;
}
Assuming that MIDI Event Command Code represents a Note On Event, then we need to interpret what Note On Event has been sent. To do this we need to cast the MidiEvent to a NoteOnEvent:
if (e.MidiEvent.CommandCode == MidiCommandCode.NoteOn)
{
// As the Command Code is a NoteOn then we need
// to cast the MidiEvent to the NoteOnEvent
NoteOnEvent ne;
ne = (NoteOnEvent)e.MidiEvent;
ne is now a NoteOnEvent which has some specific MIDI attributes, such as a NoteNumber, which is an int that represents a single note from the full scale; as well as a Velocity which represents how hard the MIDI note has been played, in this example it how hard was the MIDI controller pressed (assuming that the MIDI controller you have can report this information ala levels of sensitivity).
Each NoteNumber represents an incremental note on the scale, starting with C0 == 0, Db0 == 1 (C# aka D-before-0), D0 ==2, Eb0 ==3 (D#), E0 == 4 etc. this relationship continues on. For practical purposes (read number of samples for the Piano scale that I have, tops out at 96 which is C8) two sets of notes have been mapped within the NAudioInterface class, in a single array. The first set of notes, 0 – 100 are consider mf (quite). Notes 100 – 200 represent the same positions, but contain samples loaded that are ff (loud). Separating by a round 100 makes all the additions and subtractions to interface with these notes rather straight forward. This mapping is contained within the vKeys Class and is a whole heap of excitement, if a long list of static mappings is your thing. A snip-it of the class:
public static class vKeys
<SNIP>
vFFKeysFileNames[48] = "ff.C4.wav";
vFFKeysFileNames[49] = "ff.Db4.wav";
vFFKeysFileNames[50] = "ff.D4.wav";
vFFKeysFileNames[51] = "ff.Eb4.wav";
vFFKeysFileNames[52] = "ff.E4.wav";
<SNIP>
Ohh Ahh..
Back to the velocity, so we have a number, ne.Velocity which represents how hard the note has been played, as such we use that to then work out what sample should be played. If it’s less then 50, then the quite sample is played, else loud.
if (ne.Velocity < 50)
{
AudioInterface.NAudioInterface.Play(ne.NoteNumber);
}
else
{
AudioInterface.NAudioInterface.Play(ne.NoteNumber + 100);
}
}
Stoping It
This means that we can now play a note and conversely we need to be able to stop playing a note. To fulfil this requirement we have the following, which is effectively the converse with the exception that no checking of the Velocity is required, instead all related samples are requested to be faded out, both the loud and the soft. One may ask whys that, basically a model of the real instrument. When a single note in an instrument stops playing, all of the note stops playing. If it had first been played softly and then loudly but then has stoped being played, then the note is no longer being played – regardless of original velocity. To this end, both sets of the notes are Faded Out.
if (e.MidiEvent.CommandCode == MidiCommandCode.NoteOff)
{
NoteEvent ne;
ne = (NoteEvent)e.MidiEvent;
AudioInterface.NAudioInterface.FadeOut(ne.NoteNumber);
AudioInterface.NAudioInterface.FadeOut(ne.NoteNumber + 100);
}
Changing It
The home stretch and in fact this could easily be left off. This last section relates to a controller value being changed. The controller, at least on the
Determining if this is a ControlChange event and assuming it is, then the MidiEvent needs to be cast to a ControlChangeEvent:
if (e.MidiEvent.CommandCode == MidiCommandCode.ControlChange)
{
ControlChangeEvent cce;
cce = (ControlChangeEvent)e.MidiEvent;
if ((int)cce.Controller == 71)
{
int timeOutValue;
if (cce.ControllerValue < 127)
{
// Calculate a sliding value for the fade out based on the
// ControllerValue. This could be drematically improved..
// It is meant to be very granular at one end and more extreme
// at the other but the calculation could surley be improved.
timeOutValue = (int)Math.Exp(Math.Log(cce.ControllerValue) * 1.75);
}
else
{
timeOutValue = 100000;
}
AudioInterface.NAudioInterface.SetFadeOut(timeOutValue);
}
}
}
Finishing It
Tha, tha, that’s all folks.
For more NAudio guidance, please review the other NAudio tutorials in the series.
http://opensebj.blogspot.com/2009/02/naudio-tutorial-2-mixing-multiple-wave.html
http://opensebj.blogspot.com/2009/03/naudio-tutorial-3-sample-properties.html
http://opensebj.blogspot.com/2009/03/naudio-tutorials-minor-note.html
http://opensebj.blogspot.com/2009/03/naudio-tutorial-4-sample-reversing.html
http://opensebj.blogspot.com/2009/04/naudio-tutorial-5-recording-audio.html