//- loading ------------------------------------------------------------------- //read in a midi seqence from a standard midi file public static Sequence readMidiFile(String filename) { MidiInStream stream = new MidiInStream(filename); //read midi file header String sig = stream.getString(4); uint hdrsize = stream.getFour(); int fileFormat = stream.getTwo(); int trackCount = stream.getTwo(); int division = stream.getTwo(); if (!sig.Equals("MThd") || hdrsize != 6) { throw new MidiFileException(filename + " is not a valid MIDI file ", 0); } Sequence seq = new Sequence(division); loadTrackZeroData(stream, seq); //read midi track data for (int trackNum = 1; trackNum < trackCount; trackNum++) { curTrackNum = trackNum; loadTrackData(stream, seq, trackNum); } finalizeTracks(seq); return(seq); }
//read data for a single event in a track's data, convert the event's delta time to an absolute track time private static Event loadEventData(MidiInStream stream) { Event evt = null; int status = stream.getOne(); if (status < 0x80) //running status { stream.pushBack(1); status = runningStatus; } if (status >= 0x80 && status < 0xff) //message event { Message msg = loadMessageData(stream, status); runningStatus = status; evt = new MessageEvent(msg); } else if (status == 0xff) //meta event { evt = loadMetaEventData(stream); //this may return null for unrecognized events } else { throw new MidiFileException(stream.filename + " has an invalid event at", stream.getDataPos() - 8); } return(evt); }
//read data from a single track chunk private static void loadTrackData(MidiInStream stream, Sequence seq, int trackNum) { //read track header String trackSig = stream.getString(4); uint trackDataLength = stream.getFour(); if (!trackSig.Equals("MTrk")) { throw new MidiFileException(stream.filename + " has an invalid track at ", stream.getDataPos() - 8); } Track track = seq.addTrack(); //get new track from sequence track.setName("Track " + trackNum.ToString()); int currentTime = 0; //event time in ticks runningStatus = 0; sysexCont = false; prevSysEx = null; Event evt = null; int startpos = stream.getDataPos(); while ((stream.getDataPos() - startpos) < trackDataLength) { currentTime += (int)stream.getVariableLengthVal(); //add delta time to current num of ticks evt = loadEventData(stream); if ((evt != null) && !(evt is EndofTrackEvent || evt is MarkerEvent || evt is CuePointEvent || //these are ignored in tracks other than track 0 evt is TempoEvent || evt is SMPTEOffsetEvent || evt is TimeSignatureEvent || evt is KeySignatureEvent)) { track.addEvent(evt, currentTime); } } //last event in track must be an "end of track" event if ((evt != null) && !(evt is EndofTrackEvent)) { throw new MidiFileException(stream.filename + ": track " + curTrackNum.ToString() + "missing end of track event at ", stream.getDataPos() - 8); } }
//build the tempo, meter and marker maps from tempo message from track 0 private static void loadTrackZeroData(MidiInStream stream, Sequence seq) { //read track header String trackSig = stream.getString(4); uint trackDataLength = stream.getFour(); if (!trackSig.Equals("MTrk")) { throw new MidiFileException(stream.filename + " has an invalid track 0 at ", stream.getDataPos() - 8); } int currentTime = 0; //event time in ticks runningStatus = 0; sysexCont = false; prevSysEx = null; Event evt; Meter prevMeter = null; int startpos = stream.getDataPos(); while ((stream.getDataPos() - startpos) < trackDataLength) { currentTime += (int)stream.getVariableLengthVal(); //add delta time to current num of ticks evt = loadEventData(stream); if (evt is TempoEvent) { Tempo tempo = new Tempo(currentTime, ((TempoEvent)evt).tempo); seq.tempoMap.addTempo(tempo); } else if (evt is SMPTEOffsetEvent) //not handling smpte timing yet { } else if (evt is TimeSignatureEvent) { int keysig = (prevMeter != null) ? prevMeter.keysig : 0; Meter meter = new Meter(currentTime, ((TimeSignatureEvent)evt).numer, ((TimeSignatureEvent)evt).denom, keysig); seq.meterMap.addMeter(meter); prevMeter = meter; } else if (evt is KeySignatureEvent) { int numer = 4; int denom = 4; if (prevMeter != null) { numer = prevMeter.numer; denom = prevMeter.denom; } Meter meter = new Meter(currentTime, numer, denom, ((KeySignatureEvent)evt).keySig); seq.meterMap.addMeter(meter); prevMeter = meter; } else if (evt is MarkerEvent) { } else if (evt is CuePointEvent) { } } }
//read data for known meta events & skip any we don't recognize private static MetaEvent loadMetaEventData(MidiInStream stream) { MetaEvent meta = null; int metatype = stream.getOne(); int metalen = (int)stream.getVariableLengthVal(); switch (metatype) { case 0x00: if (metalen == 0) { meta = new SequenceNumberEvent(curTrackNum); } if (metalen >= 2) { int val = stream.getTwo(); metalen -= 2; meta = new SequenceNumberEvent(val); } break; //text events case 0x01: String txt = stream.getString(metalen); metalen = 0; meta = new TextEvent(txt); break; case 0x02: String copyright = stream.getString(metalen); metalen = 0; meta = new CopyrightEvent(copyright); break; case 0x03: String trackname = stream.getString(metalen); metalen = 0; meta = new TrackNameEvent(trackname); break; case 0x04: String instrument = stream.getString(metalen); metalen = 0; meta = new InstrumentEvent(instrument); break; case 0x05: String lyric = stream.getString(metalen); metalen = 0; meta = new LyricEvent(lyric); break; case 0x06: String marker = stream.getString(metalen); metalen = 0; meta = new MarkerEvent(marker); break; case 0x07: String cue = stream.getString(metalen); metalen = 0; meta = new CuePointEvent(cue); break; case 0x08: String patchname = stream.getString(metalen); metalen = 0; meta = new PatchNameEvent(patchname); break; case 0x09: String devname = stream.getString(metalen); metalen = 0; meta = new DeviceNameEvent(devname); break; //obsolete events case 0x20: int chanNum = stream.getOne(); metalen -= 1; meta = new MidiChannelEvent(chanNum); break; case 0x21: int portNum = stream.getOne(); metalen -= 1; meta = new MidiPortEvent(portNum); break; //end of track event case 0x2f: meta = new EndofTrackEvent(); break; //timing events case 0x51: int t1 = stream.getTwo(); int t2 = stream.getOne(); int tempo = (t1 * 256) + t2; metalen -= 3; meta = new TempoEvent(tempo); break; case 0x54: int hr = stream.getOne(); int rr = (hr / 32) % 4; int hh = hr % 32; int mn = stream.getOne(); int se = stream.getOne(); int fr = stream.getOne(); int ff = stream.getOne(); metalen -= 5; meta = new SMPTEOffsetEvent(rr, hh, mn, se, fr, ff); break; case 0x58: int nn = stream.getOne(); int b1 = stream.getOne(); int dd = (int)Math.Pow(2.0, b1); int cc = stream.getOne(); int bb = stream.getOne(); metalen -= 4; meta = new TimeSignatureEvent(nn, dd, cc, bb); break; case 0x59: int sf = stream.getOne(); int mi = stream.getOne(); metalen -= 2; meta = new KeySignatureEvent(sf, mi); break; //other people's events case 0x7f: List <byte> propdata = stream.getRange(metalen); metalen = 0; meta = new ProprietaryEvent(propdata); break; //skip any other events default: break; } stream.skipBytes(metalen); //skip unknown events & any extra bytes at the end of known events runningStatus = 0; //meta events cancel running status return(meta); }
//read data for a midi message (80 - ff), handle sysex continuation and escape sequences private static Message loadMessageData(MidiInStream stream, int status) { Message msg = null; if (status < 0xF0) //midi channel message { int msgtype = status / 16; int channel = status % 16; int b1 = stream.getOne(); int b2 = 0; if ((msgtype != 0xC) && (msgtype != 0xD)) { b2 = stream.getOne(); } msg = Message.getChannelMessage(msgtype, channel, b1, b2); } else if (status == 0xF0) //sysex message { int len = stream.getOne(); List <byte> sysExData = stream.getRange(len); sysexCont = (sysExData[sysExData.Count - 1] != 0xf7); //is the last byte of sysex data a F7? msg = new SysExMessage(sysExData); prevSysEx = (SysExMessage)msg; runningStatus = 0; //sysex msg cancel running status } else if (status == 0xF7) { if (sysexCont) //sysex continuation - append this data to prev sysex message { int len = stream.getOne(); List <byte> contData = stream.getRange(len); sysexCont = (contData[contData.Count - 1] != 0xf7); //is the last byte of sysex data a F7? prevSysEx.sysExData.AddRange(contData); } else { //escape sequence int len = stream.getOne(); List <byte> escData = stream.getRange(len); msg = new EscapeMessage(escData); } runningStatus = 0; } else { //system common msgs shouldn't occur here, but if they do, we need to skip them int b1 = 0; int b2 = 0; int datalen = SystemMessage.SysMsgLen[status - 0xF0] - 1; if (datalen > 0) { b1 = stream.getOne(); } if (datalen > 1) { b2 = stream.getOne(); b1 = ((b1 % 128) * 128) + (b2 % 128); } msg = new SystemMessage(status, b1); runningStatus = 0; } return(msg); }