Example #1
0
        public void Write(Stream strm)
        {
            this.stream = strm;

            trackData.Clear();

            stream.Write(TrackHeader, 0, TrackHeader.Length);

            foreach (MidiEvent e in track.Iterator())
            {
                WriteVariableLengthValue(e.DeltaTicks);

                switch (e.MidiMessage.MessageType)
                {
                case MessageType.Channel:
                    Write((ChannelMessage)e.MidiMessage);
                    break;

                case MessageType.SystemExclusive:
                    Write((SysExMessage)e.MidiMessage);
                    break;

                case MessageType.Meta:
                    Write((MetaMessage)e.MidiMessage);
                    break;

                case MessageType.SystemCommon:
                    Write((SysCommonMessage)e.MidiMessage);
                    break;

                case MessageType.SystemRealtime:
                    Write((SysRealtimeMessage)e.MidiMessage);
                    break;

                case MessageType.Short:
                    Write((ShortMessage)e.MidiMessage);
                    break;
                }
            }

            byte[] trackLength = BitConverter.GetBytes(trackData.Count);

            if (BitConverter.IsLittleEndian)
            {
                Array.Reverse(trackLength);
            }

            stream.Write(trackLength, 0, trackLength.Length);

            foreach (byte b in trackData)
            {
                stream.WriteByte(b);
            }
        }
Example #2
0
    public static NotationTrack parseMidiTrack(Midi.Track track, int division, ref Regex fingerPattern)
    {
        int microsecondsPerBeat = Midi.PpqnClock.DefaultTempo;

        float time = 0;

        TrackStatus    trackStatus = new TrackStatus();
        FingerChordMap fingerMap   = new FingerChordMap();

        List <Note> notes = new List <Note>();

        Regex fingerMetaPattern = new Regex("fingering-marker-pattern:(.*)");

        string name = "";

        foreach (Midi.MidiEvent e in track.Iterator())
        {
            time += e.DeltaTicks * microsecondsPerBeat / (division * 1000);

            switch (e.MidiMessage.MessageType)
            {
            case Midi.MessageType.Meta:
                Midi.MetaMessage mm = e.MidiMessage as Midi.MetaMessage;
                switch (mm.MetaType)
                {
                case Midi.MetaType.Tempo:
                    Midi.TempoChangeBuilder builder = new Midi.TempoChangeBuilder(mm);
                    microsecondsPerBeat = builder.Tempo;

                    break;

                case Midi.MetaType.Text:
                {
                    string text  = Encoding.Default.GetString(mm.GetBytes());
                    var    match = fingerMetaPattern.Match(text);
                    if (match.Success)
                    {
                        fingerPattern = new Regex(match.Groups[1].ToString());
                        Debug.LogFormat("Finger Pattern found: {0}", fingerPattern.ToString());
                    }
                }

                break;

                case Midi.MetaType.Marker:
                    if (fingerPattern != null)
                    {
                        string text  = Encoding.Default.GetString(mm.GetBytes());
                        var    match = fingerPattern.Match(text);
                        if (match.Success)
                        {
                            //Debug.LogFormat("Finger: {0}", text);
                            try
                            {
                                int    pitch  = int.Parse(match.Groups[1].ToString());
                                Finger finger = (Finger)int.Parse(match.Groups[2].ToString());

                                if (!fingerMap.ContainsKey(e.AbsoluteTicks))
                                {
                                    fingerMap[e.AbsoluteTicks] = new FingerChord();
                                }

                                fingerMap[e.AbsoluteTicks][pitch] = finger;
                            }
                            catch (System.Exception except)
                            {
                                Debug.LogWarningFormat("fingering marker parse failed: {0}, {1}", text, except.Message);
                            }
                        }
                        //else
                        //	Debug.LogWarningFormat("fail marker: {0}", text);
                    }

                    break;

                case Midi.MetaType.TrackName:
                    name = Encoding.Default.GetString(mm.GetBytes());

                    break;
                }

                break;

            case Midi.MessageType.Channel:
                Midi.ChannelMessage cm = e.MidiMessage as Midi.ChannelMessage;

                if (!trackStatus.ContainsKey(cm.MidiChannel))
                {
                    trackStatus[cm.MidiChannel] = new ChannelStatus();
                }

                var commandType = cm.Command;
                if (commandType == Midi.ChannelCommand.NoteOn && cm.Data2 == 0)
                {
                    commandType = Midi.ChannelCommand.NoteOff;
                }

                switch (commandType)
                {
                case Midi.ChannelCommand.NoteOn:
                {
                    int pitch    = cm.Data1;
                    int velocity = cm.Data2;

                    if (pitch >= Piano.PitchMin && pitch <= Piano.PitchMax)
                    {
                        trackStatus[cm.MidiChannel][pitch] = new PitchStatus {
                            tick = e.AbsoluteTicks, startTime = time, velocity = velocity
                        }
                    }
                    ;
                }

                break;

                case Midi.ChannelCommand.NoteOff:
                {
                    int pitch = cm.Data1;

                    if (!trackStatus[cm.MidiChannel].ContainsKey(pitch))
                    {
                        Debug.LogWarningFormat("Unexpected noteOff: {0}, {1}", e.AbsoluteTicks, pitch);
                    }
                    else
                    {
                        PitchStatus status = trackStatus[cm.MidiChannel][pitch];

                        Note note = new Note {
                            tick = status.tick, start = status.startTime, duration = time - status.startTime, pitch = pitch, velocity = status.velocity
                        };

                        if (fingerMap.ContainsKey(note.tick) && fingerMap[note.tick].ContainsKey(note.pitch))
                        {
                            note.finger = fingerMap[note.tick][note.pitch];
                        }

                        notes.Add(note);
                    }
                }

                break;
                }

                break;
            }
        }

        NotationTrack notation = new NotationTrack();

        notation.notes = notes.ToArray();
        notation.name  = name;

        return(notation);
    }
Example #3
0
        // usage :
        // string mydocpath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
        // StreamWriter outputFile = new StreamWriter(mydocpath + @"\WriteLines.txt")

        public void Write(StreamWriter strm)
        {
            this.stream = strm;
            int  trackid        = 0;
            bool bHeaderCreated = false;



            if (sequence.tracks[0].ContainsNotes == false)
            {
                // first track has no notes
                // Create header
                //0, 0, Header, format, nTracks, division
                stream.WriteLine(string.Format("0, 0, Header, {0}, {1}, {2}", sequence.Format, sequence.tracks.Count, sequence.Division));
            }
            else
            {
                // If first track contains notes, create an empty track with header information (so tracks.count + 1)

                //0, 0, Header, format, nTracks, division
                stream.WriteLine(string.Format("0, 0, Header, {0}, {1}, {2}", sequence.Format, sequence.tracks.Count + 1, sequence.Division));

                trackid++;

                // First track is for informations
                stream.WriteLine("1, 0, Start_track");

                // informations
                stream.WriteLine(string.Format("1, 0, Title_t, \"{0}\"", song));
                stream.WriteLine("1, 0, Text_t, \"Midi file dump made with Karaboss\"");
                stream.WriteLine("1, 0, Copyright_t, \"No copyright\"");

                // Track, Time, Time_signature, Num, Denom, Click, NotesQ
                // FAB: TO BE CHECKED
                stream.WriteLine(string.Format("1, 0, Time_signature, {0}, {1}, {2}, {3}", sequence.Time.Numerator, sequence.Time.Denominator, 24, 8));
                stream.WriteLine(string.Format("1, 0, Tempo, {0}", sequence.Tempo));
                stream.WriteLine("1, 0, End_track");

                bHeaderCreated = true;
            }



            for (int i = 0; i < sequence.tracks.Count; i++)
            {
                runningStatus = 0;
                track         = sequence.tracks[i];

                trackid++;

                #region track informations
                // Start new track
                stream.WriteLine(string.Format("{0}, 0, Start_track", trackid));

                if (!bHeaderCreated)
                {
                    // first track has no notes
                    // header was not created
                    // informations
                    stream.WriteLine(string.Format("1, 0, Title_t, \"{0}\"", song));
                    stream.WriteLine("1, 0, Text_t, \"Midi file dump made with Karaboss\"");
                    stream.WriteLine("1, 0, Copyright_t, \"No copyright\"");

                    // Track, Time, Time_signature, Num, Denom, Click, NotesQ
                    // FAB: TO BE CHECKED
                    stream.WriteLine(string.Format("1, 0, Time_signature, {0}, {1}, {2}, {3}", sequence.Time.Numerator, sequence.Time.Denominator, 24, 8));
                    stream.WriteLine(string.Format("1, 0, Tempo, {0}", sequence.Tempo));
                    bHeaderCreated = true;
                }
                else
                {
                    // Track, Time, Title_t, Text
                    stream.WriteLine(string.Format("{0}, 0, Title_t, \"{1}\"", trackid, track.Name));

                    // Track, Time, Instrument_name_t, Text
                    stream.WriteLine(string.Format("{0}, 0, Instrument_name_t, \"{1}\"", trackid, MidiFile.PCtoInstrument(track.ProgramChange)));

                    // Track, Time, Program_c, Channel, Program_num
                    stream.WriteLine(string.Format("{0}, 0, Program_c, {1}, {2}", trackid, track.MidiChannel, track.ProgramChange));
                }

                #endregion

                #region events
                foreach (MidiEvent e in track.Iterator())
                {
                    switch (e.MidiMessage.MessageType)
                    {
                    case MessageType.Channel:
                        Write((ChannelMessage)e.MidiMessage, trackid, e.AbsoluteTicks, track.MidiChannel);
                        break;

                    case MessageType.SystemExclusive:
                        Write((SysExMessage)e.MidiMessage);
                        break;

                    case MessageType.Meta:
                        Write((MetaMessage)e.MidiMessage, trackid, e.AbsoluteTicks);
                        break;

                    case MessageType.SystemCommon:
                        Write((SysCommonMessage)e.MidiMessage);
                        break;

                    case MessageType.SystemRealtime:
                        Write((SysRealtimeMessage)e.MidiMessage);
                        break;
                    }
                }
                #endregion

                //Track, Time, End_track
                stream.WriteLine(string.Format("{0}, 0, End_track", trackid));
            }

            // Last record
            // 0, 0, End_of_file
            stream.WriteLine("0, 0, End_of_file");

            // Close the stream
            stream.Close();
        }
Example #4
0
        private IEnumerable <int> GetTickIterator(MidiToolkit.Track track, Vst.Plugin plugin, int startPosition)
        {
            IEnumerator <MidiToolkit.MidiEvent> enumerator = track.Iterator().GetEnumerator();

            bool hasNext;
            bool breakLoop = false;

            for (hasNext = enumerator.MoveNext();
                 hasNext && enumerator.Current.AbsoluteTicks < startPosition;
                 hasNext = enumerator.MoveNext())
            {
            }

            int ticks = startPosition;

            while (hasNext)
            {
                while (ticks < enumerator.Current.AbsoluteTicks)
                {
                    if (ticks >= _length)
                    {
                        breakLoop = true;
                        break;
                    }

                    yield return(ticks);

                    ticks++;
                }

                if (breakLoop)
                {
                    break;
                }

                yield return(ticks);

                while (hasNext && enumerator.Current.AbsoluteTicks == ticks)
                {
                    if (enumerator.Current.MidiMessage is MidiToolkit.ChannelMessage cm)
                    {
                        if (cm.Command == MidiToolkit.ChannelCommand.NoteOn)
                        {
                            _masterSequencer.SendNoteOn(plugin, (byte)cm.Data1, (byte)cm.Data2);
                            _pressedNotes[cm.Data1] = true;
                        }
                        else if (cm.Command == MidiToolkit.ChannelCommand.NoteOff)
                        {
                            _masterSequencer.SendNoteOff(plugin, (byte)cm.Data1);
                            _pressedNotes[cm.Data1] = false;
                        }
                    }

                    hasNext = enumerator.MoveNext();
                }

                ticks++;
            }

            for (byte i = 0; i < 128; i++)
            {
                if (_pressedNotes[i])
                {
                    _masterSequencer.SendNoteOff(plugin, i);
                }
            }

            _numOfPlayingScores--;
        }
Example #5
0
        /// <summary>
        /// Creates a notechart from the specified midi path and the actual charttype
        /// (i.e. ExpertSingle from notes.mid).  Due to the overhead necessary to
        /// parse a midi file.  I am going to cram all midi->chart operations into
        /// one function call.
        /// This function uses the Sanford midi parser.  While it is horribly slow
        /// on larger (e.g. RB) midis, it works without a hitch on every midi I've
        /// come across.
        /// </summary>
        /// <param name="chartSelection">
        /// The information on which particular notechart to use.
        /// </param>
        /// <param name="chartInfo">The metadata on the chart.</param>
        /// <param name="BPMChanges">The list of BPM changes for this chart.</param>
        /// <returns>
        /// A filled out Notechart containing the needed information from the *.mid file.
        /// </returns>
        public static Notes ParseMidiInformationSanford(ChartSelection chartSelection,
            Info chartInfo,
            List<BPMChange> BPMChanges)
        {
            Notes notechartToReturn = new Notes();
            notechartToReturn.instrument = chartSelection.instrument;
            notechartToReturn.difficulty = chartSelection.difficulty;

            // The following two switch's are used to get the proper midi terminology for
            // the selected track and difficulty.
            string instrumentPart = null;
            int greenKey = 0;
            int redKey = 0;
            int yellowKey = 0;
            int blueKey = 0;
            int orangeKey = 0;

            switch (chartSelection.instrument)
            {
                case "Single":
                    instrumentPart = "PART GUITAR";
                    break;
                case "DoubleGuitar":
                    instrumentPart = "PART GUITAR COOP";
                    break;
                case "DoubleBass":
                    instrumentPart = "PART BASS";
                    break;
                case "Drums":
                    instrumentPart = "PART DRUMS";
                    break;
                default:
                    instrumentPart = "PART GUITAR";
                    break;
            }

            switch (chartSelection.difficulty)
            {
                case "Expert":
                    greenKey = 96;
                    redKey = 97;
                    yellowKey = 98;
                    blueKey = 99;
                    orangeKey = 100;
                    break;
                case "Hard":
                    greenKey = 84;
                    redKey = 85;
                    yellowKey = 86;
                    blueKey = 87;
                    orangeKey = 88;
                    break;
                case "Medium":
                    greenKey = 72;
                    redKey = 73;
                    yellowKey = 74;
                    blueKey = 75;
                    orangeKey = 76;
                    break;
                case "Easy":
                    greenKey = 60;
                    redKey = 61;
                    yellowKey = 62;
                    blueKey = 63;
                    orangeKey = 64;
                    break;
                default:
                    greenKey = 96;
                    redKey = 97;
                    yellowKey = 98;
                    blueKey = 99;
                    orangeKey = 100;
                    break;
            }

            Sequence mySequence = new Sequence(chartSelection.directory + "\\notes.mid");
            Track trackToUse = new Track();
            chartInfo.resolution = mySequence.Division;

            // Go through each event in the first track (which contains the BPM changes)
            // and parse the resulting string.
            Track sanTrack = mySequence[0];
            foreach (Sanford.Multimedia.Midi.MidiEvent currEvent in sanTrack.Iterator())
            {
                if (currEvent.MidiMessage.MessageType == MessageType.Meta)
                {
                    MetaMessage currMessage = currEvent.MidiMessage as MetaMessage;
                    //currTickValue += Convert.ToUInt32(splitEventString[1]);
                    if (currMessage.MetaType == MetaType.Tempo)
                    {
                        TempoChangeBuilder tempoBuilder = new TempoChangeBuilder(currMessage);
                        int midiBPMChange = tempoBuilder.Tempo;
                        // In midi files, bpm chages are stored as "microseconds per quarter note"
                        // and must be converted to BPM, and then into the non decimal format the game
                        // uses.
                        double currBPMDouble = 60000000 / (double)midiBPMChange;
                        uint BPMToAdd = (uint)(currBPMDouble * 1000);
                        BPMChanges.Add(new BPMChange((uint)currEvent.AbsoluteTicks, (uint)BPMToAdd));
                    }
                }
            }

            // Find the specified instrument's track
            for (int i = 1; i < mySequence.Count; i++)
            {
                sanTrack = mySequence[i];
                Sanford.Multimedia.Midi.MidiEvent currEvent = sanTrack.GetMidiEvent(0);
                if (currEvent.MidiMessage.MessageType == MessageType.Meta)
                {
                    MetaMessage currMessage = currEvent.MidiMessage as MetaMessage;
                    if (currMessage.MetaType == MetaType.TrackName)
                    {
                        MetaTextBuilder trackName = new MetaTextBuilder(currMessage);

                        // -If we come across a "T1 GEMS" track, we're in GH1 territory.
                        // -GH2/FoF has both PART BASS and PART RHYTHM (one or the other depending
                        //  on the chart).
                        if ((trackName.Text == instrumentPart) || (trackName.Text == "T1 GEMS") ||
                            ((trackName.Text == "PART RHYTHM") && (instrumentPart == "PART BASS")))
                        {
                            trackToUse = sanTrack;
                        }
                    }
                }
            }

            Note currNote = new Note();
            bool blankNote = true;
            // Scan through and record every note specific to the selected difficulty
            foreach (Sanford.Multimedia.Midi.MidiEvent currEvent in trackToUse.Iterator())
            {
                // We need to specify wether a note is blank or not so we don't add
                // blank notes from other difficulties into the chart, but if we have
                // a filled out note, any nonzero tick value means we are moving to a
                // new note, so we must cut our ties and add this note to the chart.
                if ((currEvent.DeltaTicks != 0) && !blankNote)
                {
                    notechartToReturn.notes.Add(currNote);
                    currNote = new Note();
                    blankNote = true;
                }

                if (currEvent.MidiMessage.MessageType == MessageType.Channel)
                {
                    ChannelMessage currMessage = currEvent.MidiMessage as ChannelMessage;
                    if (currMessage.Command == ChannelCommand.NoteOn)
                    {
                        // Only consider notes within the octave our difficulty is in.
                        if (((currMessage.Data1 == greenKey) || (currMessage.Data1 == redKey) ||
                            (currMessage.Data1 == yellowKey) || (currMessage.Data1 == blueKey) ||
                            (currMessage.Data1 == orangeKey)) && (currMessage.Data2 != 0))
                        {
                            // If it's a new note, we need to setup the tick value of it.
                            if (blankNote)
                            {
                                //currNote.TickValue = totalTickValue;
                                currNote.tickValue = (uint)currEvent.AbsoluteTicks;
                                blankNote = false;
                            }
                            if (currMessage.Data1 == greenKey) { currNote.addNote(0); }
                            else if (currMessage.Data1 == redKey) { currNote.addNote(1); }
                            else if (currMessage.Data1 == yellowKey) { currNote.addNote(2); }
                            else if (currMessage.Data1 == blueKey) { currNote.addNote(3); }
                            else if (currMessage.Data1 == orangeKey) { currNote.addNote(4); }
                        }
                    }

                }
            }

            return notechartToReturn;
        }
        static Dictionary<int, List<int[]>> getInstrumentNoteTimes(Track track, int[,] instrumentsAtTicks, Dictionary<int, List<int[]>> eventsAtTicksDict)
        {
            //Dictionary<int, List<int[]>> eventsAtTicksDict = new Dictionary<int, List<int[]>>();

            //dict to be modified to allow note duration calculations
            Dictionary<Tuple<int, int>, int[]> noteDurationData = new Dictionary<Tuple<int, int>, int[]>(); //key = <channel,pitch> data = <abs, vel>
            IEnumerable<MidiEvent> midievents = track.Iterator();
            foreach (MidiEvent midievent in midievents)
            {
                //Console.Write("{0:X}, {1}, {2}", midievent.MidiMessage.Status, midievent.AbsoluteTicks, "bytes: ");
                //foreach (byte b in midievent.MidiMessage.GetBytes()) Console.Write("{0:X}, {1}", b, " ");
                //Console.WriteLine();

                switch (midievent.MidiMessage.Status >> 4)
                {
                    case 0x9:

                        byte[] byteHold = new byte[4];
                        byteHold = midievent.MidiMessage.GetBytes();
                        int channel = byteHold[0] & 0x0F;//bitwise and to get last 4 bits
                        int pitch = byteHold[1];
                        int vel = byteHold[2];
                        int abs = midievent.AbsoluteTicks;
                        Tuple<int, int> key = new Tuple<int, int>(channel, pitch);

                        int[] data = new int[3] { abs, vel, channel };

                        if (!noteDurationData.ContainsKey(key))
                        {
                            noteDurationData.Add(key, data);
                        }
                        else
                        {
                            //Console.WriteLine("too stupid for real msg");
                            //NOD stands for note-on duplicate. We need this because occasionally, songs like to send multiple NONs before a NOFF. I find this confusing and frustrating.
                            byte[] byteHoldNOFF_NOD = new byte[4];
                            byteHoldNOFF_NOD = midievent.MidiMessage.GetBytes();
                            int channelNOFF_NOD = byteHoldNOFF_NOD[0] & 0x0F;//bitwise and to get last 4 bits
                            int pitchNOFF_NOD = byteHoldNOFF_NOD[1];
                            int absNOFF_NOD = midievent.AbsoluteTicks;
                            Tuple<int, int> keyNOFF_NOD = new Tuple<int, int>(channelNOFF_NOD, pitchNOFF_NOD);

                            int[] NONdata_NOD = noteDurationData[keyNOFF_NOD];
                            noteDurationData.Remove(keyNOFF_NOD);
                            int absNON_NOD = NONdata_NOD[0];
                            int velNON_NOD = NONdata_NOD[1];
                            int dur = absNOFF_NOD - absNON_NOD;
                            int instr = -1; //deal with it hopfully/
                            //CALCULATE current instrument
                            for (int i = 0; i < instrumentsAtTicks.GetLength(0); ++i)
                            {
                                if (instrumentsAtTicks[i, 16] > absNON_NOD)
                                {
                                    instr = instrumentsAtTicks[i - 1, channelNOFF_NOD]; //this makes sense. finding a bigger time then backtracking should never fail
                                }
                                else if (i == instrumentsAtTicks.GetLength(0) - 1)
                                {
                                    instr = instrumentsAtTicks[i, channelNOFF_NOD]; //added this case because I was wrong in the previous comment.
                                }
                            }
                            int[] eventdata = new int[5] { instr, pitchNOFF_NOD, velNON_NOD, dur, channelNOFF_NOD }; //to be added to the dictionary of lists of lists
                            if (!(NONdata_NOD[1] == 0))
                            {
                                eventsAtTicksDict = addToDictHandleCollisions(absNON_NOD, eventdata, eventsAtTicksDict);
                            }
                            //pretend nothing happened. To be clear, in this try block, we are killing the duplicated note and restarting with a new note.
                            noteDurationData.Add(key, data);

                        }
                        break;

                    case 0x8:

                        byte[] byteHoldNOFF = new byte[4];
                        byteHoldNOFF = midievent.MidiMessage.GetBytes();
                        int channelNOFF = byteHoldNOFF[0] & 0x0F;//bitwise and to get last 4 bits
                        int pitchNOFF = byteHoldNOFF[1];
                        int absNOFF = midievent.AbsoluteTicks;
                        Tuple<int, int> keyNOFF = new Tuple<int, int>(channelNOFF, pitchNOFF);
                        if (noteDurationData.ContainsKey(keyNOFF))
                        {
                            int[] NONdata = noteDurationData[keyNOFF];
                            noteDurationData.Remove(keyNOFF);
                            int absNON = NONdata[0];
                            int velNON = NONdata[1];
                            int dur = absNOFF - absNON;
                            int instr = -1;
                            //CALCULATE current instrument
                            for (int i = 0; i < instrumentsAtTicks.GetLength(0); ++i)
                            {
                                if (instrumentsAtTicks[i, 16] > absNON)
                                {
                                    instr = instrumentsAtTicks[i - 1, channelNOFF]; //this makes sense. finding a bigger time then backtracking should never fail
                                }
                                else if (i == instrumentsAtTicks.GetLength(0) - 1)
                                {
                                    instr = instrumentsAtTicks[i, channelNOFF]; //added this case because I was wrong in the previous comment.
                                }
                            }
                            int[] eventdata = new int[5] { instr, pitchNOFF, velNON, dur, channelNOFF };
                            eventsAtTicksDict = addToDictHandleCollisions(absNON, eventdata, eventsAtTicksDict);
                        }
                        break;

                    default:
                        break;
                }
            }

            return eventsAtTicksDict;
        }
Example #7
0
        static void Main(string[] args)
        {
            bool outputMidiVelocity = false;

            //var filename = "01-v-drac.s3m";
            //S3MFile f = S3MFile.Parse(filename);
            //var tempo = 60000000 / (f.Header.InitialTempo);
            //byte numerator = 4;
            //byte denominator = 4;
            //var speed = 3;
            //var skip = 0;

            var filename = "02-v-bewm.s3m";
            S3MFile f = S3MFile.Parse(filename);
            var tempo = 60000000 / (f.Header.InitialTempo);
            byte numerator = 4;
            byte denominator = 4;
            var speed = 3;
            var skip = 0;
            outputMidiVelocity = true;

            /*
            var filename = "V-OPTION.S3M";
            S3MFile f = S3MFile.Parse(filename);
            var tempo = 60000000 / (f.Header.InitialTempo);
            byte numerator = 6;
            byte denominator = 8;
            var speed = 3;
            var skip = 4;
              */
            /*
            var filename = "V-FALCON.S3M";
            S3MFile f = S3MFile.Parse(filename);
            var tempo = 60000000 / (1 * f.Header.InitialTempo);
            byte numerator = 4;
            byte denominator = 4;
            var speed = 3;
            var skip = 0;
            */
            /*
            var filename = "V-CONTRA.IT";
            S3MFile f = S3MFile.Parse(filename);
            var tempo = 60000000 / (1 * f.Header.InitialTempo);
            byte numerator = 4;
            byte denominator = 4;
            var speed = 3;
            var skip = 0;
             * */

            /*
            var filename = "V-BLAST.S3M";
            S3MFile f = S3MFile.Parse(filename);
            var tempo = 60000000 / (1 * f.Header.InitialTempo);
            byte numerator = 4;
            byte denominator = 4;
            var speed = 3;
            var skip = 0;*/

            Sequence s = new Sequence();
            Track t1 = new Track();
            Track t2 = new Track();
            Track t3 = new Track();
            Track drums = new Track();
            Track kick = new Track();
            Track timeTrack = new Track();
            s.Add(timeTrack);
            s.Add(t1);
            s.Add(t2);
            s.Add(t3);
            //s.Add(drums);
            //s.Add(kick);

            var drumPC = new ChannelMessageBuilder();
            drumPC.MidiChannel = 09;
            drumPC.Command = ChannelCommand.ProgramChange;
            drumPC.Data1 = 0;// 79 + ce.Instrument;
            drumPC.Build();
            drums.Insert(0, drumPC.Result);

            kick.Insert(0, drumPC.Result);

            TimeSignatureBuilder tsb = new TimeSignatureBuilder();
            tsb.Numerator = numerator;
            tsb.Denominator = denominator;
            tsb.ClocksPerMetronomeClick = 24;
            tsb.ThirtySecondNotesPerQuarterNote = 8;

            tsb.Build();

            timeTrack.Insert(0, tsb.Result);

            TempoChangeBuilder tcb = new TempoChangeBuilder();
            tcb.Tempo = tempo;
            tcb.Build();

            timeTrack.Insert(0, tcb.Result);

            var outputPatterns = new int[] { 5, 6, 7};

            //var c1 = (from p in f.OrderedPatterns
            //          where patterns.Contains(p.PatternNumber)
            //          select p)
            //         .SelectMany(p => p.Channels)
            //         .Where(c => c.ChannelNumber == 1)
            //         .SelectMany(ce => ce.ChannelEvents);

            //var c2 = (from p in f.OrderedPatterns
            //          where patterns.Contains(p.PatternNumber)
            //          select p)
            //         .SelectMany(p => p.Channels)
            //         .Where(c => c.ChannelNumber == 2)
            //         .SelectMany(ce => ce.ChannelEvents);

            //var c3 = (from p in f.OrderedPatterns
            //          where patterns.Contains(p.PatternNumber)
            //          select p)
            //         .SelectMany(p => p.Channels)
            //         .Where(c => c.ChannelNumber == 3)
            //         .SelectMany(ce => ce.ChannelEvents);

            var patterns = from p in f.OrderedPatterns.Skip(skip) /*where outputPatterns.Contains(p.PatternNumber) */select p;
            //.SelectMany(p => p.Rows);

            Dictionary<int, TrackInfo> tracks = new Dictionary<int, TrackInfo>();
            tracks.Add(1, new TrackInfo(t1));
            tracks[1].Channel = 0;
            tracks.Add(2, new TrackInfo(t2));
            tracks[2].Channel = 1;
            tracks.Add(3, new TrackInfo(t3));
            tracks[3].Channel = 2;
            var di = new TrackInfo(drums);
            di.Channel = 9;
            var mapper = new Func<ChannelEvent, int>(ce =>
            {
                switch (ce.Instrument)
                {
                    case 6:
                        return 42;
                    case 8:
                        return 35;
                    case 9:
                        return 38;
                    case 10:
                        return 47;
                    default:
                        Debug.Fail(ce.Instrument.ToString());
                        return 0;

                }
            });
            di.NoteMapper = mapper;
            var kickInfo = new TrackInfo(kick);
            kickInfo.Channel = 9;
            kickInfo.NoteMapper = mapper;
            tracks.Add(4, di);
            tracks.Add(5, kickInfo);

            var tick = 0;
            foreach (var pattern in patterns)
            {
                bool breakPattern = false;
                foreach (var row in pattern.Rows)
                {
                    //if ((row.RowNumber-1) % 16 == 0)
                    //{
                    //    drums.Insert(tick, kickOn);
                    //    drums.Insert(tick + speed, kickOff);
                    //}
                    foreach (var ce in row.ChannelEvents)
                    {
                        if (ce == null)
                        {
                            //Console.Out.WriteLine("skip");
                            continue;
                        }

                        if (ce.Command == 3)
                        {
                            var modulo = row.RowNumber % 32;
                            if (modulo != 0)
                            {
                                // sad to say, not sure why mod 8 but divide by 16 works
                                var m8 = row.RowNumber % 8;
                                if (m8 == 0)
                                {
                                    var restore = tsb.Result;
                                    tsb.Numerator = (byte)(row.RowNumber / 16);
                                    tsb.Build();
                                    var change = tsb.Result;
                                    timeTrack.Insert(tick, change);
                                    timeTrack.Insert(tick + 3, restore);
                                }

                            }
                            //Debugger.Break();
                            // pattern break
                            // figure out the time signature of the pattern up to this point
                            // then go back and insert a time signature change message at the beginning
                            // of the pattern
                            // and another one at the end to revert
                            //var restore = tsb.Result;
                            //tsb.Numerator = 2;
                            //tsb.Build();
                            //var change = tsb.Result;
                            //timeTrack.Insert(rowStartTick, change);
                            //timeTrack.Insert(tick + 3, restore);
                            breakPattern = true;
                        }

                        if (!tracks.ContainsKey(ce.ChannelNumber)) continue;
                        TrackInfo ti = tracks[ce.ChannelNumber];

                        if (ce.Note == -1 )
                        {
                            // skip
                        }
                        else if( ce.Note == 0xFF)
                        {
                            // re-use current note, but change instrument

                            //Console.Out.WriteLine("skip");
                        }
                        else if (ce.Note == 0xFE) //off
                        {
                            if (ti.LastPitch != -1)
                            {
                                NoteOff(tick, ti);
                            }
                        }
                        else
                        {
                            //if (ce.Volume != -1 && ce.Volume < 32) continue;
                            if (ti.LastPitch != -1)
                            {
                                NoteOff(tick, ti);
                            }

                            //p = ProgramChange(ti.Track, tick, p, ce);

                            var delay = 0;
                            if (ce.Command == 19)
                            {
                                if (208 <= ce.Data && ce.Data <= 214)  // HACK: 214 is a guess at the top range of SD commands
                                {
                                    delay = ce.Data - 208;
                                    Debug.Assert(delay >= 0);
                                }
                            }

                            NoteOn(tick + delay, ti, ce);

                        }

                    }
                    tick += speed;
                    if (breakPattern)
                    {
                        break;
                    }
                }
            }

            foreach (var pair in tracks)
            {
                if (pair.Value.LastPitch != -1)
                {
                    NoteOff(tick, pair.Value);
                }
            }

            foreach (MidiEvent m in drums.Iterator().Take(5))
            {
                if (m.MidiMessage is ChannelMessage)
                {
                    ChannelMessage cm = (ChannelMessage)m.MidiMessage;
                    Console.Out.WriteLine("{0} {1}", m.AbsoluteTicks, cm.Command);
                }
            }

            s.Save(Path.ChangeExtension(filename, ".mid"));
            /*
            var tick = 0;
            var speed = 3;
            var lastNote = -1;
            var p = -1;
            foreach (var ce in c1)
            {
                if (ce == null || ce.Note == -1 || ce.Note == 0xFF)
                {
                    tick += speed;
                    Console.Out.WriteLine("skip");
                    continue;
                }
                else if (ce.Note == 0xFE) //off
                {
                    lastNote = NoteOff(t, tick, lastNote);
                }
                else
                {
                    if (lastNote != -1)
                    {
                        lastNote = NoteOff(t, tick, lastNote);
                    }

                    p = ProgramChange(t, tick, p, ce);

                    var delay = 0;
                    if (ce.Command == 19)
                    {
                        if (208 <= ce.Data && ce.Data <= 214)  // HACK: 214 is a guess at the top range of SD commands
                        {
                            delay = ce.Data - 208;
                            Debug.Assert(delay >= 0);
                        }
                    }

                    lastNote = NoteOn(t, tick + delay, ce.Note);

                }
                tick += speed;
            }
            lastNote = NoteOff(t, tick, lastNote);
             *
             * */
        }