static UInt64 readTracks(byte[] fileData, MIDIData data, UInt64 chunkStart) { UInt64 index = chunkStart; while (index < (UInt64)fileData.Length) { // start reading a track chunk if ((UInt64)fileData.Length < index + 8) { throw new Exception("File data too short to contain track chunk at index " + index); } if (!(fileData[index + 0] == 0x4d && fileData[index + 1] == 0x54 && fileData[index + 2] == 0x72 && fileData[index + 3] == 0x6B)) // "MTrk" { throw new Exception("File data does not contain expected track identifier at index " + index); } index += 4; ushort chunklen = 0; chunklen |= (ushort)((ushort)fileData[index + 0] << 24); chunklen |= (ushort)((ushort)fileData[index + 1] << 16); chunklen |= (ushort)((ushort)fileData[index + 2] << 8); chunklen |= (ushort)((ushort)fileData[index + 3]); index += 4; Console.WriteLine("Chunklen {0}", chunklen); if ((UInt64)fileData.Length < index + chunklen) { throw new Exception("File data too short to contain track chunk of length " + chunklen + " at index " + (index - 8)); } MIDITrack track = new MIDITrack(); UInt64 trackindex = index; MIDIEvent previous = new MIDIEvent(); while (trackindex < index + chunklen) { MIDIEvent new_event = MIDIFileReader.readEvent(fileData, ref trackindex, previous); previous = new_event; track.events.Add(new_event); } data.tracks.Add(track); index = trackindex; break; } return(index); }
public void printEvent(MIDIEvent midievent) { switch (midievent.type) { case EventType.UnknownEvent: Console.WriteLine("{0}\tUnknown Event {1:X2} {2:X2} pos {3}", midievent.delta, midievent.val1, midievent.val2, midievent.pos); break; case EventType.MetaEvent: Console.WriteLine("{0}\tMeta Event {1} pos {2} running {3}", midievent.delta, midievent.metaeventtype, midievent.pos, midievent.running); break; case EventType.SysExEvent: Console.WriteLine("{0}\tSysEx Event {1} pos {2} running {3}", midievent.delta, midievent.sysexeeventtype, midievent.pos, midievent.running); break; case EventType.MIDIEvent: Console.WriteLine("{0}\tMIDI Event {1} pos {2} running {3}", midievent.delta, midievent.midieventtype, midievent.pos, midievent.running); break; } }
// reads event or throws exception, advances index to end of event fields // In a breathtaking display of parsimony, instructions that repeat the status // byte of the previous instruction *may be* written without that status byte. // If there isn't a high-bit byte following the delta-time, assume it's a repeated // instruction. The transmission speeds of early equipment must have been // extremely low. public static MIDIEvent readEvent(byte[] fileData, ref UInt64 index, MIDIEvent previous) { MIDIEvent new_event = new MIDIEvent(); UInt64 trackindex = index; new_event.delta = readVariableLengthQuantity(fileData, ref trackindex); new_event.pos = index; switch (fileData[trackindex] >> 4 & 0xF) // first 4 bits { case 0x8: new_event.type = EventType.MIDIEvent; new_event.midieventtype = MIDIEventType.NoteOff; new_event.val1 = (uint)(fileData[trackindex] & 0x7); // channel new_event.val2 = (uint)(fileData[trackindex + 1]); // key number new_event.val3 = (uint)(fileData[trackindex + 2]); // velocity trackindex += 3; break; case 0x9: new_event.type = EventType.MIDIEvent; new_event.midieventtype = MIDIEventType.NoteOn; new_event.val1 = (uint)(fileData[trackindex] & 0x7); // channel new_event.val2 = (uint)(fileData[trackindex + 1]); // key number new_event.val3 = (uint)(fileData[trackindex + 2]); // velocity trackindex += 3; break; case 0xA: new_event.type = EventType.MIDIEvent; new_event.midieventtype = MIDIEventType.NoteOn; new_event.val1 = (uint)(fileData[trackindex] & 0x7); // channel new_event.val2 = (uint)(fileData[trackindex + 1]); // key number new_event.val3 = (uint)(fileData[trackindex + 2]); // velocity trackindex += 3; break; case 0xB: new_event.type = EventType.MIDIEvent; new_event.midieventtype = MIDIEventType.Controller; new_event.val1 = (uint)(fileData[trackindex] & 0x7); // channel new_event.val2 = (uint)(fileData[trackindex + 1]); // controller new_event.val3 = (uint)(fileData[trackindex + 2]); // value trackindex += 3; break; case 0xC: new_event.type = EventType.MIDIEvent; new_event.midieventtype = MIDIEventType.ProgramChange; new_event.val1 = (uint)(fileData[trackindex] & 0x7); // channel new_event.val2 = (uint)(fileData[trackindex + 1]); // program trackindex += 2; break; case 0xD: new_event.type = EventType.MIDIEvent; new_event.midieventtype = MIDIEventType.ChannelPresure; new_event.val1 = (uint)(fileData[trackindex] & 0x7); // channel new_event.val2 = (uint)(fileData[trackindex + 1]); // pressure trackindex += 2; break; case 0xE: new_event.type = EventType.MIDIEvent; new_event.midieventtype = MIDIEventType.PitchBend; new_event.val1 = (uint)(fileData[trackindex] & 0x7); // channel new_event.val2 = (uint)(fileData[trackindex + 1]); // lsb new_event.val3 = (uint)(fileData[trackindex + 2]); // msb trackindex += 3; break; case 0xF: if ((fileData[trackindex] & 0xF) == 0xF) // Meta event { new_event.type = EventType.MetaEvent; ushort type = (ushort)fileData[trackindex + 1]; trackindex = trackindex + 2; uint length = MIDIFileReader.readVariableLengthQuantity(fileData, ref trackindex); // trackindex updated to end of length field if (trackindex + length > (UInt64)fileData.Length) { throw new Exception("File data too short to contain metadata event of length " + length + " at index " + (trackindex)); } switch (type) { case 0x00: new_event.metaeventtype = MetaEventType.SequenceNumber; if (length != 2) { throw new Exception("Metadata sequence number event with incorrect length " + length + " (should be 2) found at index " + (trackindex)); } new_event.val1 = (ushort)((fileData[trackindex] << 8) | fileData[trackindex + 1]); break; case 0x01: new_event.metaeventtype = MetaEventType.Text; new_event.message = MIDIFileReader.readEventMessage(fileData, trackindex, length); break; case 0x02: new_event.metaeventtype = MetaEventType.Copyright; new_event.message = MIDIFileReader.readEventMessage(fileData, trackindex, length); break; case 0x03: new_event.metaeventtype = MetaEventType.SequenceTrackName; new_event.message = MIDIFileReader.readEventMessage(fileData, trackindex, length); break; case 0x04: new_event.metaeventtype = MetaEventType.InstrumentName; new_event.message = MIDIFileReader.readEventMessage(fileData, trackindex, length); break; case 0x05: new_event.metaeventtype = MetaEventType.Lyric; new_event.message = MIDIFileReader.readEventMessage(fileData, trackindex, length); break; case 0x06: new_event.metaeventtype = MetaEventType.Marker; new_event.message = MIDIFileReader.readEventMessage(fileData, trackindex, length); break; case 0x07: new_event.metaeventtype = MetaEventType.CuePoint; new_event.message = MIDIFileReader.readEventMessage(fileData, trackindex, length); break; case 0x08: new_event.metaeventtype = MetaEventType.ProgramName; new_event.message = MIDIFileReader.readEventMessage(fileData, trackindex, length); break; case 0x09: new_event.metaeventtype = MetaEventType.DeviceName; new_event.message = MIDIFileReader.readEventMessage(fileData, trackindex, length); break; case 0x20: new_event.metaeventtype = MetaEventType.MIDIChannelPrefix; if (length != 0x01) { throw new Exception("Metadata MIDI channel prefix event with incorrect length " + length + " (should be 1) found at index " + (trackindex)); } new_event.val1 = fileData[trackindex]; break; case 0x21: new_event.metaeventtype = MetaEventType.MIDIPort; if (length != 0x01) { throw new Exception("Metadata MIDI port event with incorrect length " + length + " (should be 1) found at index " + (trackindex)); } new_event.val1 = fileData[trackindex]; break; case 0x2F: new_event.metaeventtype = MetaEventType.EndOfTrack; if (length != 0) { throw new Exception("Metadata end of track event with incorrect length " + length + " (should be 0) found at index " + (trackindex)); } break; case 0x51: new_event.metaeventtype = MetaEventType.Tempo; if (length != 0x03) { throw new Exception("Metadata tempo event with incorrect length " + length + " (should be 3) found at index " + (trackindex)); } new_event.val1 = (uint)(fileData[trackindex] << 16 | fileData[trackindex + 1] << 8 | fileData[trackindex + 2]); break; case 0x54: new_event.metaeventtype = MetaEventType.SMPTEOffset; if (length != 0x05) { throw new Exception("Metadata SMPTE offset event with incorrect length " + length + " (should be 5) found at index " + (trackindex)); } new_event.val1 = (uint)(fileData[trackindex]); new_event.val2 = (uint)(fileData[trackindex + 1]); new_event.val3 = (uint)(fileData[trackindex + 2]); new_event.val4 = (uint)(fileData[trackindex + 3]); new_event.val5 = (uint)(fileData[trackindex + 4]); break; case 0x58: new_event.metaeventtype = MetaEventType.TimeSignature; if (length != 0x04) { throw new Exception("Metadata time signature event with incorrect length " + length + " (should be 4) found at index " + (trackindex)); } new_event.val1 = (uint)(fileData[trackindex]); new_event.val2 = (uint)(fileData[trackindex + 1]); new_event.val3 = (uint)(fileData[trackindex + 2]); new_event.val4 = (uint)(fileData[trackindex + 3]); break; case 0x59: new_event.metaeventtype = MetaEventType.KeySignature; if (length != 0x02) { throw new Exception("Metadata key signature event with incorrect length " + length + " (should be 2) found at index " + (trackindex)); } new_event.val1 = (uint)(fileData[trackindex]); new_event.val2 = (uint)(fileData[trackindex + 1]); break; case 0x7F: new_event.metaeventtype = MetaEventType.SequencerSpecificEvent; new_event.message = MIDIFileReader.readEventMessage(fileData, trackindex, length); break; default: throw new Exception("Unknown metadata field type " + type + " found at index " + (trackindex)); break; } trackindex += length; } else // SysEx event (or error) { if (fileData[trackindex] == 0xF0) { new_event.type = EventType.SysExEvent; trackindex += 2; UInt64 temp = trackindex - 1; uint length = MIDIFileReader.readVariableLengthQuantity(fileData, ref trackindex); // trackindex updated to end of length field new_event.message = MIDIFileReader.readEventMessage(fileData, trackindex, length); if (new_event.message[new_event.message.Count - 1] == 0xF7) { new_event.sysexeeventtype = SysExEventType.SingleEvent; } if (new_event.message[new_event.message.Count - 1] == 0x00) { new_event.sysexeeventtype = SysExEventType.ContinuationEvent; uint delta_time = MIDIFileReader.readVariableLengthQuantity(fileData, ref trackindex); } } else { // hope that this is only two bytes long Console.WriteLine("Unknown event at index {0} with opening byte {1:X2}", trackindex, fileData[trackindex]); new_event.type = EventType.UnknownEvent; new_event.val1 = fileData[trackindex]; //fileData[trackindex + 1]; new_event.val2 = fileData[trackindex + 1]; trackindex += 2; //throw new Exception(String.Format("Unknown field found {0:X2} at index {1}", fileData[trackindex], trackindex)); } } break; default: // okay, might be a running status. Check the provided previous event, which must be a MIDI event rather than a sysex or meta event if (previous.type != EventType.MIDIEvent) { throw new Exception(String.Format("Undefined event start {0:X2} byte at index {1} ", fileData[trackindex], trackindex)); } switch (previous.midieventtype) { case MIDIEventType.NoteOff: new_event.midieventtype = previous.midieventtype; new_event.val1 = previous.val1; new_event.val2 = fileData[trackindex]; new_event.val3 = fileData[trackindex + 1]; trackindex += 2; new_event.running = true; break; case MIDIEventType.NoteOn: new_event.midieventtype = previous.midieventtype; new_event.val1 = previous.val1; new_event.val2 = fileData[trackindex]; new_event.val3 = fileData[trackindex + 1]; trackindex += 2; new_event.running = true; break; case MIDIEventType.PolyphonicPressure: new_event.midieventtype = previous.midieventtype; new_event.val1 = previous.val1; new_event.val2 = fileData[trackindex]; new_event.val3 = fileData[trackindex + 1]; trackindex += 2; new_event.running = true; break; case MIDIEventType.Controller: new_event.midieventtype = previous.midieventtype; new_event.val1 = previous.val1; new_event.val2 = fileData[trackindex]; new_event.val3 = fileData[trackindex + 1]; trackindex += 2; new_event.running = true; break; case MIDIEventType.ProgramChange: new_event.midieventtype = previous.midieventtype; new_event.val1 = previous.val1; new_event.val2 = fileData[trackindex]; trackindex += 1; new_event.running = true; break; case MIDIEventType.ChannelPresure: new_event.midieventtype = previous.midieventtype; new_event.val1 = previous.val1; new_event.val2 = fileData[trackindex]; trackindex += 1; new_event.running = true; break; case MIDIEventType.PitchBend: new_event.midieventtype = previous.midieventtype; new_event.val1 = previous.val1; new_event.val2 = fileData[trackindex]; new_event.val3 = fileData[trackindex + 1]; trackindex += 2; new_event.running = true; break; default: throw new Exception(String.Format("Undefined event start {0:X2} byte at index {1} ", fileData[trackindex], trackindex)); break; } break; } index = trackindex; return(new_event); }
static void Main(string[] args) { WaveFormat waveFormat = new WaveFormat(44100, 16, 1); Oscillator freqOsc = new Oscillator(Oscillator.WaveType.SineWave, waveFormat.SampleRate, new ConstantScalar(0.1f), new ConstantScalar(80.0f), new ConstantScalar(0), new ConstantScalar(100.0f)); Oscillator ampOscVel = new Oscillator(Oscillator.WaveType.SineWave, waveFormat.SampleRate, new ConstantScalar(0.05f), new ConstantScalar(8.0f), new ConstantScalar(0.5f), new ConstantScalar(8.0f)); Oscillator ampOsc = new Oscillator(Oscillator.WaveType.SawtoothWave, waveFormat.SampleRate, ampOscVel, new ConstantScalar(0.5f), new ConstantScalar(0), new ConstantScalar(1.1f)); Oscillator variableFreq = new Oscillator(Oscillator.WaveType.SineWave, waveFormat.SampleRate, freqOsc, ampOsc, new ConstantScalar(0), new ConstantScalar(0)); ScalarPassthrough scalarPassthru = new ScalarPassthrough(waveFormat, variableFreq); NoiseGenerator noiseGen = new NoiseGenerator(waveFormat, NoiseGenerator.NoiseType.WhiteNoise); Oscillator freqsweep = new Oscillator(Oscillator.WaveType.SineWave, waveFormat.SampleRate, new ConstantScalar(0.1f), new ConstantScalar(200), new ConstantScalar(0), new ConstantScalar(440)); Oscillator qsweep = new Oscillator(Oscillator.WaveType.SineWave, waveFormat.SampleRate, new ConstantScalar(1.0f), new ConstantScalar(0.9f), new ConstantScalar(0), new ConstantScalar(1.0f)); //new ConstantScalar((float)(1.0 / Math.Sqrt(2))); //Oscillator gainSweep = new Oscillator(Oscillator.WaveType.TriangleWave, waveFormat.SampleRate, new ConstantScalar(0.025f), new ConstantScalar(2), new ConstantScalar(0), new ConstantScalar(6)); ConstantScalar gainSweep = new ConstantScalar(6); SampleProcessor.SampleProcessor filter = new SampleProcessor.NotchFilter(waveFormat, freqsweep, qsweep, gainSweep, scalarPassthru); // distortion "pedal" on a minimally modified hard body electric guitar signal Oscillator gainOsc = new Oscillator(Oscillator.WaveType.SineWave, waveFormat.SampleRate, new ConstantScalar(0.5f), new ConstantScalar(2), new ConstantScalar(0), new ConstantScalar(5)); Oscillator cutoffOsc = new Oscillator(Oscillator.WaveType.SineWave, waveFormat.SampleRate, new ConstantScalar(0.95f), new ConstantScalar(3), new ConstantScalar(0), new ConstantScalar(4)); ConstantScalar cutoff = new ConstantScalar(0.2f); IWaveSource cleanguitar = CodecFactory.Instance.GetCodec(@"..\..\sampledata\guitar-sample.mp3"); // sample signal from single-pickup electric guitar. Recorded 8/14/20. IReadableAudioSource <float> convertedguitar = cleanguitar.ToMono().ToSampleSource(); SampleProcessor.DistortionEffect distortion = new SampleProcessor.DistortionEffect(waveFormat, gainOsc, cutoff, convertedguitar); SampleProcessor.SampleProcessor reverbEffect = new SampleProcessor.ReverbEffect(waveFormat, new ConstantScalar(0.4f), new ConstantScalar(0.5f), waveFormat.SampleRate, distortion); BasicAudioController basicAudioController = new BasicAudioController(GetSoundOut(), 1, 44100); basicAudioController.addSource((ISampleSource)reverbEffect); basicAudioController.startPlaying(); Console.ReadKey(); basicAudioController.stopPlaying(); return; MIDI.MIDIData data = MIDI.MIDIFileReader.readFile(@"..\..\sampledata\MIDI_sample.mid"); MIDI.MIDIData testdata = new MIDI.MIDIData(); testdata.format = 0; testdata.timing = MIDI.TimingScheme.TimeCode; testdata.timecode_fps = 24; testdata.timecode_sfr = 4; testdata.ntracks = 1; testdata.tracks.Add(new MIDI.MIDITrack()); MIDI.MIDIEvent event0 = new MIDI.MIDIEvent(); MIDI.MIDIEvent event1 = new MIDI.MIDIEvent(); event0.delta = 4 * 24 * 5; // wait 5 seconds from start event1.delta = 4 * 24 * 2; // 2 seconds //event0.type = MIDI.EventType.MIDIEvent; //event0.midieventtype = MIDI.MIDIEventType.NoteOn; //event0.val1 = 0; // channel 0 //event0.val2 = 60; // middle C //event0.val3 = 1; //event1.type = MIDI.EventType.MIDIEvent; //event1.midieventtype = MIDI.MIDIEventType.NoteOff; //event1.val1 = 0; // channel 0 //event1.val2 = 60; // middle C //event1.val3 = 1; //testdata.tracks[0].events.Add(event0); //testdata.tracks[0].events.Add(event1); MIDI.MIDIPlayer player = new MIDI.MIDIPlayer(data); Console.WriteLine("Track 1 events:"); player.playTrack(1); Console.ReadKey(); return; MIDIAudioController audioController = new MIDIAudioController(GetSoundOut()); ChromaticScale.ChromaticScale scale = new ChromaticScale.ChromaticScale(); audioController.startPlaying(); SampleSource.WaveGenerator.WaveType wavetype = WaveGenerator.WaveType.SineWave; Console.ReadKey(); // wait for input for (int i = 0; i < scale.notes.Count(); i++) { float freq = scale.notes[i].base_freq / 2; Console.WriteLine(scale.notes[i].identifier[0] + " - " + freq + " - " + wavetype); audioController.updatePlaying(freq, wavetype); Console.ReadKey(); // wait for input wavetype = Next(wavetype); } for (int i = 0; i < scale.notes.Count(); i++) { Console.WriteLine(scale.notes[i].identifier[0] + " - " + scale.notes[i].base_freq.ToString() + " - " + wavetype); audioController.updatePlaying(scale.notes[i].base_freq, wavetype); Console.ReadKey(); // wait for input wavetype = Next(wavetype); } for (int i = 0; i < scale.notes.Count(); i++) { float freq = scale.notes[i].base_freq * 2; Console.WriteLine(scale.notes[i].identifier[0] + " - " + freq + " - " + wavetype); audioController.updatePlaying(freq, wavetype); Console.ReadKey(); // wait for input wavetype = Next(wavetype); } audioController.stopPlaying(); audioController.Dispose(); }
public void playTrack(int trackIndex) { if (trackIndex >= data.tracks.Count) { throw new Exception(String.Format("Invalid track index {0} specified. Highest available track index is {1}", trackIndex, data.tracks.Count - 1)); } // determine timing increments UInt64 time_inc_us = 1; UInt64 time_offset_us = 0; // start time offset if (data.timing == TimingScheme.MetricalTiming) { // locate tempo event in track 0 (format 0 or 1) or playing track for format 2 UInt64 us_per_quarternote = 500000; // 120 bpm default (I think) switch (data.format) { case 0: for (int i = 0; i < data.tracks[trackIndex].events.Count; i++) { if (data.tracks[trackIndex].events[i].type == EventType.MetaEvent && data.tracks[trackIndex].events[i].metaeventtype == MetaEventType.Tempo) { Console.WriteLine("Tempo event with value {0} ({0:X2})", data.tracks[trackIndex].events[i].val1); us_per_quarternote = data.tracks[trackIndex].events[i].val1; time_inc_us = us_per_quarternote / data.tickdiv; break; } } break; case 1: for (int i = 0; i < data.tracks[0].events.Count; i++) { if (data.tracks[0].events[i].type == EventType.MetaEvent && data.tracks[0].events[i].metaeventtype == MetaEventType.Tempo) { Console.WriteLine("Tempo event with value {0} ({0:X2})", data.tracks[0].events[i].val1); us_per_quarternote = data.tracks[0].events[i].val1; time_inc_us = us_per_quarternote / data.tickdiv; // should give us/p break; } else if (data.tracks[0].events[i].type == EventType.MetaEvent && data.tracks[0].events[i].metaeventtype == MetaEventType.TimeSignature) { Console.WriteLine("TimeSignature event with value {0} {1} {2} {3} ({0:X2} {1:X2} {2:X2} {3:X2})", data.tracks[0].events[i].val1, data.tracks[0].events[i].val2, data.tracks[0].events[i].val3, data.tracks[0].events[i].val4); break; } } break; case 2: for (int i = 0; i < data.tracks[trackIndex].events.Count; i++) { if (data.tracks[trackIndex].events[i].type == EventType.MetaEvent && data.tracks[trackIndex].events[i].metaeventtype == MetaEventType.Tempo) { Console.WriteLine("Tempo event with value {0} ({0:X2})", data.tracks[trackIndex].events[i].val1); us_per_quarternote = data.tracks[trackIndex].events[i].val1; time_inc_us = us_per_quarternote / data.tickdiv; break; } } break; default: break; } } else { // 1 tick is the sub-frame resolution time_inc_us = (UInt64)(1000000 / (data.timecode_fps * data.timecode_sfr)); } UInt64 ticks = 0; // ticks are in microseconds int event_index = 0; UInt64 curr_ticks = data.tracks[trackIndex].events[0].delta; MIDIAudioController audioController = new MIDIAudioController(GetSoundOut()); audioController.startPlayingMidi(); while (event_index < data.tracks[trackIndex].events.Count) { while (curr_ticks == 0 && event_index < data.tracks[trackIndex].events.Count) { if (data.tracks[trackIndex].events[event_index].type == EventType.MIDIEvent) { MIDIEvent the_event = data.tracks[trackIndex].events[event_index]; if (the_event.val1 == 0) // channel 0 for note events { switch (the_event.midieventtype) { case MIDIEventType.NoteOn: audioController.startPlayingMIDIKey((int)the_event.val2, (float)the_event.val3 * 0.0001f); Console.WriteLine("NoteOn {0} {1}", the_event.val2, the_event.val3); break; case MIDIEventType.NoteOff: audioController.stopPlayingMIDIKey((int)the_event.val2, (float)the_event.val3 * 0.0001f); Console.WriteLine("NoteOff {0} {1}", the_event.val2, the_event.val3); break; default: break; } } } event_index += 1; if (event_index >= data.tracks[trackIndex].events.Count) { break; } curr_ticks = data.tracks[trackIndex].events[event_index].delta; } audioController.updateMidiKeys(1.0f); // 1 increment Thread.Sleep((int)time_inc_us / 1000); ticks += time_inc_us; curr_ticks--; if (event_index >= data.tracks[trackIndex].events.Count) { break; } //Console.WriteLine("ticks {0} event_index {1} ({2}) curr_ticks {3}", ticks, event_index, data.tracks[trackIndex].events[event_index].delta, curr_ticks); } }