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);
        }
Example #2
0
        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);
        }
Example #4
0
        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();
        }
Example #5
0
        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);
            }
        }