private static void ParseMetaEvent(MidiBigEndianReader reader, Track track, byte type, uint length, uint absoluteTime) { string text; byte[] data; switch (type) { case 0x00: // sequence number if (length != 2) { throw new FormatException("Sequence number meta event should have length == 2"); } track.MetaEvents.Add(new SequenceNumber { AbsoluteTime = absoluteTime, Number = reader.ReadUInt16() }); break; case 0x01: // text event text = reader.ReadText(length); track.MetaEvents.Add(new TextEvent { AbsoluteTime = absoluteTime, Text = text }); break; case 0x02: // copyright notice text = reader.ReadText(length); track.MetaEvents.Add(new CopyrightNotice { AbsoluteTime = absoluteTime, Text = text }); break; case 0x03: // track name text = reader.ReadText(length); track.MetaEvents.Add(new TrackName { AbsoluteTime = absoluteTime, Text = text }); break; case 0x04: // instrument name text = reader.ReadText(length); track.MetaEvents.Add(new InstrumentName { AbsoluteTime = absoluteTime, Text = text }); break; case 0x05: // lyrics text = reader.ReadText(length); track.MetaEvents.Add(new Lyrics { AbsoluteTime = absoluteTime, Text = text }); break; case 0x06: // marker text = reader.ReadText(length); track.MetaEvents.Add(new Marker { AbsoluteTime = absoluteTime, Text = text }); break; case 0x07: // cue point text = reader.ReadText(length); track.MetaEvents.Add(new CuePoint { AbsoluteTime = absoluteTime, Text = text }); break; case 0x20: // midi channel prefix if (length != 1) { throw new FormatException("midi channel prefix event should have length == 1"); } track.MetaEvents.Add(new MidiChannelPrefix { AbsoluteTime = absoluteTime, Channel = reader.ReadByte() }); break; case 0x2F: // end of track if (length != 0) { throw new FormatException("end of track event should have length == 0"); } track.MetaEvents.Add(new EndOfTrack { AbsoluteTime = absoluteTime }); break; case 0x51: // set tempo if (length != 3) { throw new FormatException("set tempo event should have length == 3"); } track.MetaEvents.Add(new SetTempo { AbsoluteTime = absoluteTime, MicrosecondsPerQuarterNote = reader.ReadUInt24() }); break; case 0x54: // SMPTE offset if (length != 5) { throw new FormatException("SMPTE offset event should have length == 5"); } track.MetaEvents.Add(new SmpteOffset { AbsoluteTime = absoluteTime, Hour = reader.ReadByte(), Minute = reader.ReadByte(), Second = reader.ReadByte(), Frame = reader.ReadByte(), SubFrame = reader.ReadByte() }); break; case 0x58: // time signature if (length != 4) { throw new FormatException("time signature event should have length == 4"); } track.MetaEvents.Add(new TimeSignature { AbsoluteTime = absoluteTime, Numerator = reader.ReadByte(), Denominator = (byte)(1 << reader.ReadByte()), MetronomePulse = reader.ReadByte(), NumberOf32ndNotesPerMidiQuarterNote = reader.ReadByte() }); break; case 0x59: // key signature if (length != 2) { throw new FormatException("key signature event should have length == 2"); } sbyte key = reader.ReadSByte(); if (key < -7 || key > 7) { throw new FormatException("Key should be from -7 to 7"); } Scale scale = reader.ReadByte() == 0 ? Scale.Major : Scale.Minor; track.MetaEvents.Add(new KeySignature { AbsoluteTime = absoluteTime, Key = new Key(scale, key) }); break; case 0x7F: // sequencer specific data = reader.ReadBytes((int)length); // skips sequencer specific data track.MetaEvents.Add(new SequencerSpecific { AbsoluteTime = absoluteTime, Data = data }); break; default: // unknown meta event data = reader.ReadBytes((int)length); // skips unknown data track.MetaEvents.Add(new UnknownMetaEvent { AbsoluteTime = absoluteTime, Data = data }); break; } }
public static Model Parse(string filename, bool computeNoteLenghts = true, bool computePitchBends = true, bool computeRealVolumes = true, bool determineInstruments = false, bool normalizeMetre = false, bool prolongSustainedNotes = false, bool showBeats = false, bool analyzeKeys = false, bool analyzeChords = false, bool playChords = false, bool discretizeBends = false, Tone?transposeTo = null) { if (playChords) { analyzeChords = true; } if (analyzeChords) { analyzeKeys = true; } if (discretizeBends) { computePitchBends = true; } if (transposeTo != null) { analyzeKeys = true; } var midi = new Model(); using (var reader = new MidiBigEndianReader(File.Open(filename, FileMode.Open))) { ParseHeaderChunk(reader, midi); foreach (var track in midi.Tracks) { ParseTrackChunk(reader, track); } if (computeNoteLenghts || normalizeMetre || showBeats) { TimeCalculator.ComputeRealTimes(midi); TimeCalculator.CreateNoteLengths(midi); } midi.Length = midi.EventsOfType <EndOfTrack>().Max(e => e.AbsoluteRealTime); if (computeRealVolumes || normalizeMetre || showBeats) { var collector = new VolumeChangeCollector(midi); collector.DetermineVolumes(); } if (prolongSustainedNotes || normalizeMetre || showBeats) { var sustainer = new Sustainer(midi); sustainer.ProlongSustainedNotes(); } if (normalizeMetre) { Normalizer.CalculateMetre(midi); Normalizer.Normalize(midi, prolongSustainedNotes); } else if (showBeats) { Normalizer.CalculateMetre(midi); Normalizer.AddBeats(midi, prolongSustainedNotes); } if (computePitchBends) { PitchBendCalculator.JoinPitchBends(midi); PitchBendCalculator.DeterminePitchRanges(midi); if (discretizeBends) { PitchBendCalculator.DiscretizeBends(midi); } } if (determineInstruments) { var collector = new InstrumentChangeCollector(midi); collector.DetermineInstruments(); } if (analyzeKeys) { if (!normalizeMetre && !showBeats) { Normalizer.CalculateMetre(midi); } if (midi.EventsOfType <KeySignature>().Any(k => k.Key.Tone != Tone.C || k.Key.Scale != Scale.Major)) { midi.Key = midi.EventsOfType <KeySignature>().First().Key; midi.IsKeyFoundByMidiItself = true; } else { MLKeyFinder.DetectKey(midi); midi.IsKeyFoundByMidiItself = false; } } if (analyzeChords) { var analyzer = new ChordAnalyzer(midi); analyzer.Analyze(); if (playChords) { analyzer.AddChordNotesToModel(); } } if (transposeTo != null) { Transposer.Transpose(midi, (Tone)transposeTo); } ChannelPlayabilityChecker.Check(midi); } return(midi); }
private static void ParseControlEvent(MidiBigEndianReader reader, Track track, byte eventType, byte channel, uint absoluteTime, ref uint size, byte nextByte) { switch (eventType) { case 0x8: //note number out of bound if (nextByte > 127) { break; } track.Channels[channel].Events.Add(new NoteOff { AbsoluteTime = absoluteTime, ChannelNumber = channel, NoteNumber = nextByte, Velocity = reader.ReadByte() }); size += 1; break; case 0x9: //note number out of bound if (nextByte > 127) { break; } track.Channels[channel].Events .Add(new NoteOn { AbsoluteTime = absoluteTime, ChannelNumber = channel, NoteNumber = nextByte, Volume = reader.ReadByte() }); size += 1; break; case 0xA: //note number out of bound if (nextByte > 127) { break; } track.Channels[channel].Events.Add(new NoteAftertouch { AbsoluteTime = absoluteTime, ChannelNumber = channel, NoteNumber = nextByte, AftertouchValue = reader.ReadByte() }); size += 1; break; case 0xB: track.Channels[channel].Events.Add(new Controller { AbsoluteTime = absoluteTime, ChannelNumber = channel, ControllerNumber = nextByte, ControllerValue = Math.Min(reader.ReadByte(), (byte)127) }); size += 1; break; case 0xC: var instrument = new MusicalInstrument(nextByte); track.Channels[channel].Events.Add(new InstrumentChange { AbsoluteTime = absoluteTime, ChannelNumber = channel, Instrument = instrument }); break; case 0xD: track.Channels[channel].Events.Add(new ChannelAftertouch { AbsoluteTime = absoluteTime, ChannelNumber = channel, AfterTouchValue = nextByte }); break; case 0xE: var lsb = nextByte; var msb = reader.ReadByte(); var pitchValue = (ushort)((msb << 7) | lsb); if (pitchValue > 16383) { throw new FormatException("Pitch value shouldn't be greater than 16383"); } track.Channels[channel].Events.Add(new PitchBend { AbsoluteTime = absoluteTime, ChannelNumber = channel, PitchValue = pitchValue }); size += 1; break; default: throw new FormatException("Unsupported event type"); } }
private static void ParseTrackChunk(MidiBigEndianReader reader, Track track) { uint chunkType = reader.ReadUInt32(); uint totalSize = reader.ReadUInt32(); uint size = 0; if (chunkType != 0x4D54726B) { reader.ReadBytes((int)totalSize); // skip if unexpeced type of chunk ParseTrackChunk(reader, track); // shouldn't recurse too deep, every chunks is usually a track chunk return; } byte lastEventTypeValue = 0; byte lastChannel = 0; uint absoluteTime = 0; while (true) { var deltaTime = reader.ReadVariableLengthValue(out byte deltaTimeLength); absoluteTime += deltaTime; size += deltaTimeLength; var eventType = reader.ReadByte(); size += 1; if (eventType >= 0x80 && eventType <= 0xEF) { // midi control event byte eventTypeValue = (byte)(eventType >> 4); byte channel = (byte)(eventType & 0x0F); lastEventTypeValue = eventTypeValue; lastChannel = channel; ParseControlEvent(reader, track, eventTypeValue, channel, absoluteTime, ref size, reader.ReadByte()); size += 1; } else if (eventType >> 4 < 0x8) { // running status control event if (lastEventTypeValue < 0x8) { throw new FormatException("No event is saved, so running status cannot be applied"); } ParseControlEvent(reader, track, lastEventTypeValue, lastChannel, absoluteTime, ref size, eventType); } else if (eventType == 0xFF) { // meta event byte type = reader.ReadByte(); size += 1; var length = reader.ReadVariableLengthValue(out byte lengthLength); size += lengthLength; ParseMetaEvent(reader, track, type, length, absoluteTime); // check end of track if (type == 0x2F) { // if end of track event was encountered, then it should be at the end of the track if (size + length == totalSize) { return; } else { throw new FormatException("Unexpected end of track"); } } size += length; lastEventTypeValue = 0; // cancel the running status } else if (eventType == 0xF0 || eventType == 0xF7) { // System exclusive events // skip them var length = reader.ReadVariableLengthValue(out byte lengthLength); reader.ReadBytes((int)length); size += lengthLength + length; lastEventTypeValue = 0; // cancel the running status } else { throw new FormatException("Unexpected event type"); } if (size > totalSize) { throw new FormatException("Track is longer than expected"); } } }