Using NAudio to generate midi

Here’s a small sample using NAudio’s midi file processing classes to write a simple set of notes to a .mid file.


using System;
using System.Collections.Generic;
using System.Linq;
using NAudio.Midi;

class Program
{
	static void Main(string[] args)
	{
		// typical args :
		//      guitar.mid E3 A3 D4 G4 B4 E5
		// or
		//      ukulele.mid G4 C4 E4 A4
		if (args.Length == 0)
		{
			Console.WriteLine("Usage: <output-filename> <list-of-notes>");
			return;
		}

		string outputFilename = args[0];

    		PitchParser pitchParser = new PitchParser();

    		var midiValues = pitchParser.Parse(args.Skip(1));

    		if (midiValues.Any())
    		{
        		var exporter = new MidiExporter();
        		exporter.SaveToFile(outputFilename, midiValues);
		}
	}
}

public class PatchParser
{
	Dictionary<string, int> patchMap = new Dictionary<string, int>();

	public PatchParser()
	{
		this.patchMap.Add("nylon", 25);
		this.patchMap.Add("steel", 26);
		this.patchMap.Add("jazz", 27);
		this.patchMap.Add("clean", 28);
		this.patchMap.Add("muted", 29);
		this.patchMap.Add("distortion", 31);
		this.patchMap.Add("bass", 33);
		this.patchMap.Add("violin", 41); 
		this.patchMap.Add("viola", 42); 
		this.patchMap.Add("cello", 43);
		this.patchMap.Add("sitar", 105); 
		this.patchMap.Add("banjo", 106); 
		this.patchMap.Add("fiddle", 111); 
	}

	public int Patch(string value)
	{
	    const int DefaultPatch = 25;
	
	    int patch = DefaultPatch;
	
	    try
	    {
	        patch = this.patchMap[value.ToLower()];
	    }
	    catch (KeyNotFoundException)
	    {
	        patch = DefaultPatch;
	    }
	
	    return patch;
	}
}

public class PitchParser
{
	Dictionary<char, int> pitchOffsets;
	Dictionary<char, int> pitchModifiers;

	public PitchParser()
	{
	    this.pitchOffsets = new Dictionary<char, int>();
	    this.pitchModifiers = new Dictionary<char, int>();
	
	    this.pitchOffsets.Add('c', 0);
	    this.pitchOffsets.Add('d', 2);
	    this.pitchOffsets.Add('e', 4);
	    this.pitchOffsets.Add('f', 5);
	    this.pitchOffsets.Add('g', 7);
	    this.pitchOffsets.Add('a', 9);
	    this.pitchOffsets.Add('b', 11);
	
	    this.pitchModifiers.Add('#', 1);
	    this.pitchModifiers.Add('b', -1);
	}

	public Pitch PitchFromString(string midiNotation)
	{
	    const int MinimumStringLength = 2;
	
	    if (midiNotation.Length < MinimumStringLength)
	        throw new ArgumentException();
	
	    string name = FindName(midiNotation);
	    string modifier = FindModifier(midiNotation);
	    int octave = FindOctave(midiNotation);
	
	    int midiValue = CalculateMidiValue(name, modifier, octave);
	
	    return new Pitch(midiValue);
	}

	public IEnumerable<Pitch> Parse(IEnumerable<string> noteList)
	{
	    var list = new List<Pitch>();
	
	    noteList.Where(note => !String.IsNullOrWhiteSpace(note)).ToList().ForEach(note => list.Add(this.PitchFromString(note)));
	
	    return list;
	}

	private string FindName(string midiNotation)
	{
	    return midiNotation.Substring(0, 1).ToLower();
	}
	
	private string FindModifier(string midiNotation)
	{
	    const int MaximumStringLength = 3;
	
	    if (midiNotation.Length == MaximumStringLength)
	    {
	        return midiNotation.Substring(1, 1).ToLower();
	    }
	
	    return string.Empty;
	}

	private int FindOctave(string midiNotation)
	{
	    const int MaximumStringLength = 3;
	    int startIndex = 1;
	
	    if (midiNotation.Length == MaximumStringLength)
	    {
	        startIndex = 2;
	    }
	
	    return Int32.Parse(midiNotation.Substring(startIndex, 1));
	}
	
	private int CalculateMidiValue(string noteName, string modifier, int octave)
	{
	    int value = 0;
	
	    try
	    {
	        value = this.pitchOffsets[noteName[0]];
	
	        if (!String.IsNullOrEmpty(modifier))
	        {
	            value += this.pitchModifiers[modifier[0]];
	        }
	    }
	    catch (KeyNotFoundException)
	    {
	        value = 0;
	    }

		const int SemitonesInOctave = 12;

		return value + (SemitonesInOctave * octave);
	}
}

public class Pitch
{
	const int MinimumMidiValue = 0;
	const int MaximumMidiValue = 255;

	public Pitch(int value)
	{
	    this.MidiValue = Math.Min(MaximumMidiValue, Math.Max(value, MinimumMidiValue));
	}

	public int MidiValue { get; private set; }
}

public class MidiExporter
{
	public void SaveToFile(string fileName, IEnumerable<Pitch> allNotes)
	{
	    const int MidiFileType = 0;
	    const int BeatsPerMinute = 60;
	    const int TicksPerQuarterNote = 120;
	    
	    const int TrackNumber = 0;
	    const int ChannelNumber = 1;
	
	    long absoluteTime = 0;
	
	    var collection = new MidiEventCollection(MidiFileType, TicksPerQuarterNote);
	
	    collection.AddEvent(new TextEvent("Note Stream", MetaEventType.TextEvent, absoluteTime), TrackNumber);
	    ++absoluteTime;
	    collection.AddEvent(new TempoEvent(CalculateMicrosecondsPerQuaterNote(BeatsPerMinute), absoluteTime), TrackNumber);
	    
	    var patchParser = new PatchParser();
	    int patchNumber = patchParser.Patch("steel");
	
	    collection.AddEvent(new PatchChangeEvent(0, ChannelNumber, patchNumber), TrackNumber);
	
	    const int NoteVelocity = 100;
	    const int NoteDuration = 3 * TicksPerQuarterNote / 4;
	    const long SpaceBetweenNotes = TicksPerQuarterNote;
	
	    foreach (var note in allNotes)
	    {
	        collection.AddEvent(new NoteOnEvent(absoluteTime, ChannelNumber, note.MidiValue, NoteVelocity, NoteDuration), TrackNumber);
	        collection.AddEvent(new NoteEvent(absoluteTime + NoteDuration, ChannelNumber, MidiCommandCode.NoteOff, note.MidiValue, 0), TrackNumber);
	
	        absoluteTime += SpaceBetweenNotes;
	    }
	
	    collection.PrepareForExport();
	    MidiFile.Export(fileName, collection);
	}

	private static int CalculateMicrosecondsPerQuaterNote(int bpm)
	{
	    return 60 * 1000 * 1000 / bpm;
	}
}