MusicTheory Help

MIDI Integration

The MusicTheory library provides seamless integration with MIDI (Musical Instrument Digital Interface) through conversion between Note objects and MIDI note numbers.

Understanding MIDI

MIDI Note Numbers

A standard numbering system from 0-127 representing musical pitches

Middle C

MIDI note 60, corresponding to C4 in scientific pitch notation

Note Range

C-1 (MIDI 0) to G9 (MIDI 127)

Enharmonic Handling

Same MIDI number can represent different note spellings (C# = Db)

MIDI Note Number Reference

Note

MIDI Number

Frequency (Hz)

A0

21

27.50

C1

24

32.70

C2

36

65.41

C3

48

130.81

C4 (Middle C)

60

261.63

A4

69

440.00

C5

72

523.25

C6

84

1046.50

C7

96

2093.00

C8

108

4186.01

Octave

MIDI Range

Note Range

-1

0-11

C-1 to B-1

0

12-23

C0 to B0

1

24-35

C1 to B1

2

36-47

C2 to B2

3

48-59

C3 to B3

4

60-71

C4 to B4

5

72-83

C5 to B5

6

84-95

C6 to B6

7

96-107

C7 to B7

8

108-119

C8 to B8

9

120-127

C9 to G9

Converting Notes to MIDI

Basic Conversion

// Note to MIDI number var c4 = new Note(NoteName.C, Alteration.Natural, 4); int midiNumber = c4.MidiNumber; // 60 var a4 = new Note(NoteName.A, Alteration.Natural, 4); int a4Midi = a4.MidiNumber; // 69 var cSharp5 = new Note(NoteName.C, Alteration.Sharp, 5); int cSharp5Midi = cSharp5.MidiNumber; // 73 // Enharmonic notes have the same MIDI number var dFlat5 = new Note(NoteName.D, Alteration.Flat, 5); int dFlat5Midi = dFlat5.MidiNumber; // 73 (same as C#5)

Handling Out-of-Range Notes

try { // This note is too low for MIDI var tooLow = new Note(NoteName.C, Alteration.Natural, -2); int midi = tooLow.MidiNumber; // Throws InvalidOperationException } catch (InvalidOperationException ex) { Console.WriteLine($"Error: {ex.Message}"); // "Note C-2 is outside the valid MIDI range (0-127)." } // Safe conversion with validation public int? GetMidiNumberSafe(Note note) { try { return note.MidiNumber; } catch (InvalidOperationException) { return null; // Note is out of MIDI range } }

Converting MIDI to Notes

MIDI to Note Conversion

// MIDI number to Note var middleC = Note.FromMidiNumber(60); Console.WriteLine(middleC); // C4 var a440 = Note.FromMidiNumber(69); Console.WriteLine(a440); // A4 // Low and high extremes var lowest = Note.FromMidiNumber(0); // C-1 var highest = Note.FromMidiNumber(127); // G9

Enharmonic Preferences

// Default conversion prefers sharps for black keys var note61 = Note.FromMidiNumber(61); Console.WriteLine(note61); // C#4 // Prefer flats for black keys var note61Flat = Note.FromMidiNumber(61, preferFlats: true); Console.WriteLine(note61Flat); // Db4 // Create a conversion preference based on key public Note MidiToNoteInKey(int midiNumber, KeySignature key) { // Prefer flats if key has flats bool useFlats = key.AccidentalCount < 0; return Note.FromMidiNumber(midiNumber, useFlats); }

Working with MIDI Chords

Chord to MIDI Numbers

public class MidiChordHelper { public static List<int> GetMidiNumbers(Chord chord) { return chord.GetNotes() .Select(note => note.MidiNumber) .ToList(); } public static Dictionary<string, int> GetMidiMap(Chord chord) { var notes = chord.GetNotes().ToList(); var degrees = new[] { "Root", "Third", "Fifth", "Seventh", "Ninth", "Eleventh", "Thirteenth" }; var map = new Dictionary<string, int>(); for (int i = 0; i < notes.Count && i < degrees.Length; i++) { map[degrees[i]] = notes[i].MidiNumber; } return map; } } // Usage var cMaj7 = new Chord(new Note(NoteName.C, 4), ChordType.Major7); var midiNumbers = MidiChordHelper.GetMidiNumbers(cMaj7); // [60, 64, 67, 71] (C, E, G, B) var midiMap = MidiChordHelper.GetMidiMap(cMaj7); // { "Root": 60, "Third": 64, "Fifth": 67, "Seventh": 71 }

MIDI Numbers to Chord

public class MidiChordAnalyzer { public static string AnalyzeChord(params int[] midiNumbers) { if (midiNumbers.Length < 3) return "Not enough notes for a chord"; // Convert to notes (prefer appropriate spelling) var notes = midiNumbers .OrderBy(m => m) .Select(m => Note.FromMidiNumber(m)) .ToList(); // Find the root (lowest note for now) var root = notes[0]; // Analyze intervals from root var intervals = notes.Skip(1) .Select(n => Interval.Between(root, n)) .ToList(); // Determine chord type based on intervals return DetermineChordType(intervals); } private static string DetermineChordType(List<Interval> intervals) { // Simplified analysis if (intervals.Count >= 2) { var third = intervals[0]; var fifth = intervals[1]; if (third.Semitones == 4 && fifth.Semitones == 7) return "Major"; if (third.Semitones == 3 && fifth.Semitones == 7) return "Minor"; if (third.Semitones == 3 && fifth.Semitones == 6) return "Diminished"; if (third.Semitones == 4 && fifth.Semitones == 8) return "Augmented"; } return "Unknown"; } } // Usage var chordType = MidiChordAnalyzer.AnalyzeChord(60, 64, 67); // "Major"

MIDI Scale Patterns

Scale to MIDI Sequence

public class MidiScaleGenerator { public static List<int> GenerateMidiScale( int rootMidi, ScaleType scaleType, int octaves = 1) { var root = Note.FromMidiNumber(rootMidi); var scale = new Scale(root, scaleType); var midiNumbers = new List<int>(); var scaleNotes = scale.GetNotes().Take(7).ToList(); // Generate for specified octaves for (int oct = 0; oct < octaves; oct++) { foreach (var note in scaleNotes) { var transposed = note.TransposeBySemitones(oct * 12); if (transposed.MidiNumber <= 127) { midiNumbers.Add(transposed.MidiNumber); } } } // Add the octave var octaveNote = root.TransposeBySemitones(octaves * 12); if (octaveNote.MidiNumber <= 127) { midiNumbers.Add(octaveNote.MidiNumber); } return midiNumbers; } } // Generate C major scale over 2 octaves var cMajorMidi = MidiScaleGenerator.GenerateMidiScale(60, ScaleType.Major, 2); // [60, 62, 64, 65, 67, 69, 71, 72, 74, 76, 77, 79, 81, 83, 84]

MIDI Velocity and Dynamics

While the library focuses on pitch, here's how to integrate with MIDI velocity:

public class MidiNoteEvent { public int MidiNumber { get; set; } public int Velocity { get; set; } public double Duration { get; set; } public MidiNoteEvent(Note note, int velocity = 64, double duration = 0.5) { MidiNumber = note.MidiNumber; Velocity = Math.Clamp(velocity, 0, 127); Duration = duration; } } public enum Dynamic { PPP = 16, // Pianississimo PP = 33, // Pianissimo P = 49, // Piano MP = 64, // Mezzo-piano MF = 80, // Mezzo-forte F = 96, // Forte FF = 112, // Fortissimo FFF = 127 // Fortississimo } // Create notes with dynamics var softNote = new MidiNoteEvent( new Note(NoteName.C, 4), (int)Dynamic.P ); var loudNote = new MidiNoteEvent( new Note(NoteName.C, 4), (int)Dynamic.FF );

Integration Examples

MIDI File Creation Helper

public class MidiTrackBuilder { private List<MidiNoteEvent> events = new List<MidiNoteEvent>(); private double currentTime = 0; public MidiTrackBuilder AddNote(Note note, double duration, int velocity = 64) { events.Add(new MidiNoteEvent(note, velocity, duration)); currentTime += duration; return this; } public MidiTrackBuilder AddChord(Chord chord, double duration, int velocity = 64) { foreach (var note in chord.GetNotes()) { events.Add(new MidiNoteEvent(note, velocity, duration)); } currentTime += duration; return this; } public MidiTrackBuilder AddScale(Scale scale, double noteDuration = 0.25, int velocity = 64) { foreach (var note in scale.GetNotes()) { events.Add(new MidiNoteEvent(note, velocity, noteDuration)); currentTime += noteDuration; } return this; } public List<MidiNoteEvent> Build() => events; } // Build a simple melody var track = new MidiTrackBuilder() .AddNote(new Note(NoteName.C, 4), 0.5) .AddNote(new Note(NoteName.D, 4), 0.5) .AddNote(new Note(NoteName.E, 4), 0.5) .AddNote(new Note(NoteName.F, 4), 0.5) .AddChord(new Chord(new Note(NoteName.C, 4), ChordType.Major), 1.0) .Build();

MIDI Input Processing

public class MidiInputProcessor { private Dictionary<int, Note> activeNotes = new Dictionary<int, Note>(); public void ProcessNoteOn(int midiNumber, int velocity) { var note = Note.FromMidiNumber(midiNumber); activeNotes[midiNumber] = note; Console.WriteLine($"Note On: {note} (MIDI {midiNumber}, Velocity {velocity})"); // Analyze currently playing notes if (activeNotes.Count >= 3) { var chordNotes = activeNotes.Values.OrderBy(n => n.MidiNumber).ToList(); // Analyze for chord detection } } public void ProcessNoteOff(int midiNumber) { if (activeNotes.TryGetValue(midiNumber, out var note)) { activeNotes.Remove(midiNumber); Console.WriteLine($"Note Off: {note} (MIDI {midiNumber})"); } } }

Best Practices

  • Validate MIDI ranges: Always check that MIDI numbers are within 0-127

  • Handle enharmonics appropriately: Choose note spellings based on musical context

  • Consider velocity: MIDI velocity (0-127) affects dynamics and expression

  • Use appropriate timing: MIDI timing is typically in ticks or milliseconds

  • Buffer management: When generating MIDI data, manage memory efficiently

Common MIDI Mappings

Drum Map (GM Standard)

public static class GeneralMidiDrums { public const int BassDrum1 = 36; public const int SnareDrum = 38; public const int ClosedHiHat = 42; public const int OpenHiHat = 46; public const int CrashCymbal1 = 49; public const int RideCymbal1 = 51; // Note: Drums are typically on MIDI channel 10 }

Program Changes

public enum GeneralMidiProgram { AcousticGrandPiano = 1, ElectricPiano1 = 5, Harpsichord = 7, Vibraphone = 12, AcousticGuitar = 25, ElectricGuitarClean = 28, DistortionGuitar = 31, AcousticBass = 33, ElectricBass = 34, Violin = 41, StringEnsemble1 = 49, Trumpet = 57, Trombone = 58, AltoSax = 66, // ... and many more }

See Also

13 June 2025