Example #1
0
        private List <MidiEvent> CreateTrack(Xmk xmk, int index)
        {
            List <MidiEvent> track = new List <MidiEvent>();

            track.Add(new NAudio.Midi.TextEvent(!string.IsNullOrEmpty(xmk.Name) ? xmk.Name : $"NOTES {index}", MetaEventType.SequenceTrackName, 0));

            foreach (var entry in xmk.Entries)
            {
                long start    = GetAbsoluteTime(entry.Start * 1000);
                long end      = GetAbsoluteTime(entry.End * 1000);
                int  velocity = entry.Unknown2 == 0 ? 100 : entry.Unknown2 % 128;

                // Text event?
                if (!string.IsNullOrEmpty(entry.Text))
                {
                    track.Add(new NAudio.Midi.TextEvent(entry.Text, MetaEventType.TextEvent, start));
                }

                if ((end - start) <= 0 || entry.Pitch > 127)
                {
                    continue;
                }

                track.Add(new NoteEvent(start, 1, MidiCommandCode.NoteOn, entry.Pitch, velocity));
                track.Add(new NoteEvent(end, 1, MidiCommandCode.NoteOff, entry.Pitch, velocity));
            }

            // Adds end track
            track.Add(new MetaEvent(MetaEventType.EndTrack, 0, track.Last().AbsoluteTime));
            return(track);
        }
Example #2
0
        private List <MidiEvent> ParseGuitar6(Xmk xmk, bool guitar = true)
        {
            MidiMapping      map   = _guitarTouchMap;
            List <MidiEvent> track = new List <MidiEvent>();

            track.Add(new NAudio.Midi.TextEvent(guitar ? "PART GUITAR" : "PART BASS", MetaEventType.SequenceTrackName, 0));

            foreach (var entry in xmk.Entries)
            {
                long start    = GetAbsoluteTime(entry.Start * 1000);
                long end      = GetAbsoluteTime(entry.End * 1000);
                int  velocity = 100;

                // Text event?
                if (!string.IsNullOrEmpty(entry.Text))
                {
                    continue;                                    // Don't write text events
                }
                //track.Add(new NAudio.Midi.TextEvent(entry.Text, MetaEventType.TextEvent, start));

                if ((end - start) <= 0 || entry.Pitch > 127)
                {
                    continue;
                }

                int pitchRemap = map[entry.Pitch];
                if (pitchRemap == -1)
                {
                    continue;
                }

                track.Add(new NoteEvent(start, 1, MidiCommandCode.NoteOn, pitchRemap, velocity));
                track.Add(new NoteEvent(end, 1, MidiCommandCode.NoteOff, pitchRemap, velocity));
            }

            // Adds play event
            var firstNote = track.Where(x => x is NoteEvent).Select(y => y as NoteEvent).OrderBy(y => y.AbsoluteTime).FirstOrDefault();

            if (firstNote != null)
            {
                var idx = track.IndexOf(firstNote);
                track.Insert(idx, new NAudio.Midi.TextEvent("[play]", MetaEventType.TextEvent, firstNote.AbsoluteTime));
            }

            // Adds idle event (end)
            var lastNote = track.Where(x => x is NoteEvent).Select(y => y as NoteEvent).OrderByDescending(y => y.AbsoluteTime).FirstOrDefault();

            if (lastNote != null)
            {
                var idx = track.IndexOf(lastNote);
                track.Insert(idx, new NAudio.Midi.TextEvent("[idle]", MetaEventType.TextEvent, lastNote.AbsoluteTime));
            }

            // Adds end track
            track.Add(new MetaEvent(MetaEventType.EndTrack, 0, track.Last().AbsoluteTime));
            return(track);
        }
Example #3
0
        private List <MidiEvent> ParseEvents(Xmk xmk)
        {
            MidiMapping      map   = _guitarMap;
            List <MidiEvent> track = new List <MidiEvent>();

            track.Add(new NAudio.Midi.TextEvent("EVENTS", MetaEventType.SequenceTrackName, 0));

            foreach (var entry in xmk.Entries)
            {
                if (string.IsNullOrEmpty(entry.Text) || entry.Unknown3 != 3)
                {
                    continue;                                                          // Practice section
                }
                long   start = GetAbsoluteTime(entry.Start * 1000);
                long   end   = GetAbsoluteTime(entry.End * 1000);
                string text  = GetPracticeName(entry.Text);

                track.Add(new NAudio.Midi.TextEvent(text, MetaEventType.TextEvent, start));
            }

            // Adds end track
            track.Add(new MetaEvent(MetaEventType.EndTrack, 0, track.Last().AbsoluteTime));
            return(track);
        }
Example #4
0
        private List <MidiEvent> ParseVocals(Xmk xmk)
        {
            List <MidiEvent> track = new List <MidiEvent>();

            track.Add(new NAudio.Midi.TextEvent("PART VOCALS", MetaEventType.SequenceTrackName, 0));

            const int VOCALS_PHRASE = 105;
            //const int VOCALS_MAX_PITCH = 84;
            const int VOCALS_MIN_PITCH = 36;

            var phraseEvents = new List <NoteEvent>();

            foreach (var entry in xmk.Entries)
            {
                long start    = GetAbsoluteTime(entry.Start * 1000);
                long end      = GetAbsoluteTime(entry.End * 1000);
                int  velocity = 100;

                if (!string.IsNullOrEmpty(entry.Text) && entry.Unknown3 == 57)
                {
                    // Lyric + pitch event
                    string text  = entry.Text;
                    int    pitch = entry.Pitch;

                    text = text.Replace("=", string.Empty);
                    text = text.Replace("@", "+");

                    if (entry.Pitch < VOCALS_MIN_PITCH)
                    {
                        text  = text + "#";
                        pitch = 60; // Middle C
                    }

                    track.Add(new NAudio.Midi.TextEvent(text, MetaEventType.Lyric, start));
                    track.Add(new NoteEvent(start, 1, MidiCommandCode.NoteOn, pitch, velocity));
                    track.Add(new NoteEvent(end, 1, MidiCommandCode.NoteOff, pitch, velocity));
                }
                else if (entry.Unknown3 == 1 && entry.Pitch == 129)
                {
                    // Vocal phrase
                    if ((end - start) <= 0)
                    {
                        end = start + DELTA_TICKS_PER_QUARTER / 4; // 1/16 note
                    }
                    phraseEvents.Add(new NoteEvent(start, 1, MidiCommandCode.NoteOn, VOCALS_PHRASE, velocity));
                    phraseEvents.Add(new NoteEvent(end, 1, MidiCommandCode.NoteOff, VOCALS_PHRASE, velocity));
                }
            }

            // Extends phrases
            for (int i = 1; i < phraseEvents.Count - 1; i += 2)
            {
                // i = end, i+1 = start
                var endTime = phraseEvents[i + 1].AbsoluteTime;
                phraseEvents[i].AbsoluteTime = endTime;
            }

            if (phraseEvents.Count > 0)
            {
                // Extends first phrase
                phraseEvents.First().AbsoluteTime = 0;

                // Extends last phrase
                phraseEvents.Last().AbsoluteTime = Math.Max(phraseEvents.Last().AbsoluteTime, track.Max(x => x.AbsoluteTime)) + DELTA_TICKS_PER_QUARTER / 8;
            }
            else
            {
                // No phrases found (Create one)
                phraseEvents.Add(new NoteEvent(0, 1, MidiCommandCode.NoteOn, VOCALS_PHRASE, 100));
                phraseEvents.Add(new NoteEvent(track.Max(x => x.AbsoluteTime) + DELTA_TICKS_PER_QUARTER / 8, 1, MidiCommandCode.NoteOff, VOCALS_PHRASE, 100));
            }

            // Shrinks phrases
            for (int i = 0; i < phraseEvents.Count; i += 2)
            {
                NoteEvent start = phraseEvents[i];
                NoteEvent end   = phraseEvents[i + 1];

                var betweenNotes = track.Where(x => x is NoteEvent &&
                                               ((NoteEvent)x).NoteNumber != VOCALS_PHRASE &&
                                               x.AbsoluteTime >= start.AbsoluteTime &&
                                               x.AbsoluteTime <= end.AbsoluteTime).ToList();

                if (betweenNotes.Count <= 0)
                {
                    continue; // No notes between phrases
                }
                var startEvent = betweenNotes
                                 .Where(x => ((NoteEvent)x).CommandCode == MidiCommandCode.NoteOn)
                                 .OrderBy(y => y.AbsoluteTime).FirstOrDefault();

                var endEvent = betweenNotes
                               .Where(x => ((NoteEvent)x).CommandCode == MidiCommandCode.NoteOff)
                               .OrderBy(y => y.AbsoluteTime).LastOrDefault();

                if (startEvent == null || endEvent == null || startEvent.AbsoluteTime > endEvent.AbsoluteTime)
                {
                    continue; // No full notes between phrases
                }
                // 1/128th note
                start.AbsoluteTime = startEvent.AbsoluteTime - (DELTA_TICKS_PER_QUARTER / 32);
                end.AbsoluteTime   = endEvent.AbsoluteTime + (DELTA_TICKS_PER_QUARTER / 32);

                track.Add(start);
                track.Add(end);
            }

            // Adds play event
            var firstNote = track.Where(x => x is NoteEvent).Select(y => y as NoteEvent).OrderBy(y => y.AbsoluteTime).FirstOrDefault();

            if (firstNote != null)
            {
                var idx = track.IndexOf(firstNote);
                track.Insert(idx, new NAudio.Midi.TextEvent("[play]", MetaEventType.TextEvent, firstNote.AbsoluteTime));
            }

            // Adds idle event (end)
            var lastNote = track.Where(x => x is NoteEvent).Select(y => y as NoteEvent).OrderByDescending(y => y.AbsoluteTime).FirstOrDefault();

            if (lastNote != null)
            {
                var idx = track.IndexOf(lastNote);
                track.Insert(idx, new NAudio.Midi.TextEvent("[idle]", MetaEventType.TextEvent, lastNote.AbsoluteTime));
            }

            // Sort by absolute time (And ensure track name is first event)
            track.Sort((x, y) => (int)(x is NAudio.Midi.TextEvent &&
                                       ((NAudio.Midi.TextEvent)x).MetaEventType == MetaEventType.SequenceTrackName
                                       ? int.MinValue : x.AbsoluteTime - y.AbsoluteTime));

            // Adds end track
            track.Add(new MetaEvent(MetaEventType.EndTrack, 0, track.Last().AbsoluteTime));
            return(track);
        }
Example #5
0
        private List <MidiEvent> ParseGuitar3(Xmk xmk, bool guitar = true)
        {
            MidiMapping      map   = _guitarMap;
            List <MidiEvent> track = new List <MidiEvent>();

            track.Add(new NAudio.Midi.TextEvent(guitar ? "PART GUITAR GHL" : "PART BASS GHL", MetaEventType.SequenceTrackName, 0));

            // Tracks HOPO on/off events
            List <NoteOnEvent> hopoNotes = new List <NoteOnEvent>();

            foreach (var entry in xmk.Entries)
            {
                long start    = GetAbsoluteTime(entry.Start * 1000);
                long end      = GetAbsoluteTime(entry.End * 1000);
                int  velocity = 100;

                // Text event?
                if (!string.IsNullOrEmpty(entry.Text))
                {
                    continue;                                    // Don't write text events
                }
                //track.Add(new NAudio.Midi.TextEvent(entry.Text, MetaEventType.TextEvent, start));

                if ((end - start) <= 0 || entry.Pitch > 127)
                {
                    continue;
                }

                if ((entry.Unknown2 & 2) == 2) // Observed 0x02, 0xCA
                {
                    // Barre chord
                    int shift = (entry.Pitch % 2 == 1) ? 1 : -1;

                    track.Add(new NoteEvent(start, 1, MidiCommandCode.NoteOn, map[entry.Pitch + shift], velocity));
                    track.Add(new NoteEvent(end, 1, MidiCommandCode.NoteOff, map[entry.Pitch + shift], velocity));
                }

                int pitchRemap = map[entry.Pitch];
                if (pitchRemap == -1)
                {
                    continue;
                }

                track.Add(new NoteEvent(start, 1, MidiCommandCode.NoteOn, pitchRemap, velocity));
                track.Add(new NoteEvent(end, 1, MidiCommandCode.NoteOff, pitchRemap, velocity));

                int hopoPitch;

                // Sets forced HOPO off pitch
                if (pitchRemap >= 94 && pitchRemap <= 100)
                {
                    hopoPitch = 102;                                        // Expert
                }
                else if (pitchRemap >= 82 && pitchRemap <= 88)
                {
                    hopoPitch = 90;                                            // Hard
                }
                else if (pitchRemap >= 70 && pitchRemap <= 76)
                {
                    hopoPitch = 78;                                            // Medium
                }
                else if (pitchRemap >= 58 && pitchRemap <= 64)
                {
                    hopoPitch = 66;                                            // Easy
                }
                else
                {
                    continue;
                }

                hopoPitch -= (entry.Unknown3 & 0x80) >> 7; // 1 = Forced HOPO
                hopoNotes.Add(new NoteOnEvent(start, 1, hopoPitch, velocity, (int)(end - start)));
            }

            // Flattens HOPO events
            var groupedNotes = hopoNotes.GroupBy(x => x.NoteNumber);

            foreach (var group in groupedNotes)
            {
                // Selects longest note at each absolute time offset
                var notes = group.GroupBy(x => x.AbsoluteTime).Select(y => y.OrderByDescending(z => z.NoteLength).First()).OrderBy(q => q.AbsoluteTime);

                NoteOnEvent prevNote = notes.First();
                track.Add(prevNote);

                foreach (var note in notes.Skip(1))
                {
                    if (note.AbsoluteTime >= prevNote.OffEvent.AbsoluteTime)
                    {
                        track.Add(prevNote.OffEvent);
                    }
                    else
                    {
                        // Overlap detected, insert note off event
                        track.Add(new NoteEvent(note.AbsoluteTime, note.Channel, MidiCommandCode.NoteOff, note.NoteNumber, note.Velocity));
                    }

                    track.Add(note);
                    prevNote = note;
                }

                // Adds last note off event
                track.Add(prevNote.OffEvent);
            }

            // Adds play event
            var firstNote = track.Where(x => x is NoteEvent).Select(y => y as NoteEvent).OrderBy(y => y.AbsoluteTime).FirstOrDefault();

            if (firstNote != null)
            {
                var idx = track.IndexOf(firstNote);
                track.Insert(idx, new NAudio.Midi.TextEvent("[play]", MetaEventType.TextEvent, firstNote.AbsoluteTime));
            }

            // Adds idle event (end)
            var lastNote = track.Where(x => x is NoteEvent).Select(y => y as NoteEvent).OrderByDescending(y => y.AbsoluteTime).FirstOrDefault();

            if (lastNote != null)
            {
                var idx = track.IndexOf(lastNote);
                track.Insert(idx, new NAudio.Midi.TextEvent("[idle]", MetaEventType.TextEvent, lastNote.AbsoluteTime));
            }

            // Sorts by absolute time
            track.Sort((x, y) =>
            {
                if (x.AbsoluteTime < y.AbsoluteTime)
                {
                    return(-1);
                }
                else if (x.AbsoluteTime > y.AbsoluteTime)
                {
                    return(1);
                }

                // Same abs time, note off goes first
                if (x.CommandCode == MidiCommandCode.NoteOff && y.CommandCode == MidiCommandCode.NoteOn)
                {
                    return(-1);
                }
                else if (x.CommandCode == MidiCommandCode.NoteOn && y.CommandCode == MidiCommandCode.NoteOff)
                {
                    return(1);
                }
                else
                {
                    return(0);
                }
            });

            // Adds end track
            track.Add(new MetaEvent(MetaEventType.EndTrack, 0, track.Last().AbsoluteTime));
            return(track);
        }
Example #6
0
        public void Export(string path, bool remap = false)
        {
            MidiEventCollection mid = new MidiEventCollection(1, DELTA_TICKS_PER_QUARTER);

            _xmks.Sort((x, y) => GetSortNumber(x.Name) - GetSortNumber(y.Name));

            int GetSortNumber(string name)
            {
                switch (name)
                {
                case "touchdrums":
                    return(1);

                case "touchguitar":
                    return(2);

                case "guitar_3x2":
                    return(3);

                case "vocals":
                    return(4);

                case "control":
                    return(5);

                default:
                    return(100);
                }
            }

            Xmk firstXmk = _xmks.FirstOrDefault();

            mid.AddTrack(CreateTempoTrack(firstXmk.TempoEntries, firstXmk.TimeSignatureEntries));

            if (!remap)
            {
                for (int i = 0; i < _xmks.Count; i++)
                {
                    mid.AddTrack(CreateTrack(_xmks[i], i));
                }
            }
            else
            {
                var eventsIdx = -1;
                for (int i = 0; i < _xmks.Count; i++)
                {
                    Xmk    xmk       = _xmks[i];
                    string trackName = !string.IsNullOrEmpty(xmk.Name) ? xmk.Name : $"NOTES {i}";

                    // Sets remapping and track name
                    switch (trackName.ToLower())
                    {
                    case "control":
                        // EVENTS
                        mid.AddTrack(ParseEvents(xmk));
                        eventsIdx = i + 1;
                        continue;

                    case "guitar_3x2":
                        // PART GUITAR GHL
                        mid.AddTrack(ParseGuitar3(xmk));
                        continue;

                    case "touchdrums":
                        // PART DRUMS
                        mid.AddTrack(ParseDrums(xmk));
                        continue;

                    case "touchguitar":
                        // PART GUITAR
                        mid.AddTrack(ParseGuitar6(xmk));
                        continue;

                    case "vocals":
                        // PART VOCALS
                        mid.AddTrack(ParseVocals(xmk));
                        continue;
                    }

                    mid.AddTrack(CreateTrack(xmk, i));
                }

                // Updates EVENTS track
                var firstPlay = mid.SelectMany(x => x)
                                .Where(y => y is TextEvent && ((TextEvent)y).Text == "[play]")
                                .OrderBy(z => z.AbsoluteTime)
                                .FirstOrDefault();

                var lastIdle = mid.SelectMany(x => x)
                               .Where(y => y is TextEvent && ((TextEvent)y).Text == "[idle]")
                               .OrderByDescending(z => z.AbsoluteTime)
                               .FirstOrDefault();

                if (eventsIdx != -1 && firstPlay != null)
                {
                    var events       = mid[eventsIdx];
                    var nextEvent    = events.OrderBy(x => x.AbsoluteTime).First(y => y.AbsoluteTime > firstPlay.AbsoluteTime);
                    var nextEventIdx = events.IndexOf(nextEvent);

                    events.Insert(nextEventIdx, new TextEvent("[music_start]", MetaEventType.TextEvent, firstPlay.AbsoluteTime));
                }

                if (eventsIdx != -1 && lastIdle != null)
                {
                    var events   = mid[eventsIdx];
                    var trackEnd = events.Last();
                    trackEnd.AbsoluteTime = lastIdle.AbsoluteTime; // Updates end track position
                    events.Remove(trackEnd);

                    events.Add(new TextEvent("[music_end]", MetaEventType.TextEvent, lastIdle.AbsoluteTime));
                    events.Add(new TextEvent("[end]", MetaEventType.TextEvent, lastIdle.AbsoluteTime));
                    events.Add(trackEnd); // Adds end track back
                }

                // Generates up/down events for BEAT track (Needed for beat markers in CH and OD in RB)
                mid.AddTrack(GenerateBeatTrack(firstXmk.TimeSignatureEntries, mid));
            }

            MidiFile.Export(path, mid);
        }
Example #7
0
 public XmkExport(Xmk xmk)
 {
     _xmks = new List <Xmk>();
     _xmks.Add(xmk);
 }
Example #8
0
        private List <MidiEvent> ParseVocals(Xmk xmk)
        {
            string nameApp = "";

            // Gets name suffix
            if (_voxRegex.IsMatch(xmk.Name))
            {
                var match = _voxRegex.Match(xmk.Name);
                nameApp = Regex.Replace($"{match.Groups[2]}", @"[-]+", " ").ToUpper();
            }

            List <MidiEvent> track = new List <MidiEvent>();

            track.Add(new NAudio.Midi.TextEvent($"PART VOCALS{nameApp}", MetaEventType.SequenceTrackName, 0));

            const int VOCALS_PHRASE    = 105;
            const int DEFAULT_VELOCITY = 100;
            const int DEFAULT_CHANNEL  = 1;
            //const int VOCALS_MAX_PITCH = 84;
            const int VOCALS_MIN_PITCH = 36;

            var phraseEvents = new List <NoteEvent>();
            var notes        = new List <(long Start, long End, string Lyric)>();

            foreach (var entry in xmk.Entries)
            {
                long start = GetAbsoluteTime(entry.Start * 1000);
                long end   = GetAbsoluteTime(entry.End * 1000);

                if (!string.IsNullOrEmpty(entry.Text) && (entry.Unknown3 == 57 ||
                                                          (xmk.Version == 5 && entry.Unknown3 == 16)))
                {
                    // Lyric + pitch event
                    string text  = entry.Text;
                    int    pitch = entry.Pitch;

                    // Get RB lyric syntax
                    text = FSGLyricToRB(text);

                    // If pitch is out of range, make unpitched - Problem solved!
                    if (entry.Pitch < VOCALS_MIN_PITCH)
                    {
                        text  = text + "#";
                        pitch = 60; // Middle C
                    }

                    track.Add(new NAudio.Midi.TextEvent(text, MetaEventType.Lyric, start));
                    track.Add(new NoteEvent(start, DEFAULT_CHANNEL, MidiCommandCode.NoteOn, pitch, DEFAULT_VELOCITY));
                    track.Add(new NoteEvent(end, DEFAULT_CHANNEL, MidiCommandCode.NoteOff, pitch, DEFAULT_VELOCITY));

                    notes.Add((start, end, text)); // Keep track of vox notes
                }
                else if ((entry.Unknown3 == 1 && entry.Pitch == 129) ||
                         (xmk.Version == 5 && entry.Unknown3 == 17 && entry.Pitch == 20))
                {
                    // Vocal phrase
                    if ((end - start) <= 0)
                    {
                        end = start + DELTA_TICKS_PER_QUARTER / 4; // 1/16 note
                    }
                    phraseEvents.Add(new NoteEvent(start, DEFAULT_CHANNEL, MidiCommandCode.NoteOn, VOCALS_PHRASE, DEFAULT_VELOCITY));
                    phraseEvents.Add(new NoteEvent(end, DEFAULT_CHANNEL, MidiCommandCode.NoteOff, VOCALS_PHRASE, DEFAULT_VELOCITY));
                }
            }

            // Extends phrases
            for (int i = 1; i < phraseEvents.Count - 1; i += 2)
            {
                // i = end, i+1 = start
                var endTime = phraseEvents[i + 1].AbsoluteTime;
                phraseEvents[i].AbsoluteTime = endTime;
            }

            if (phraseEvents.Count > 0)
            {
                var firstPhrase = phraseEvents.First();

                var firstLyric = track
                                 .Where(x => x is TextEvent && (x as TextEvent).MetaEventType == MetaEventType.Lyric)
                                 .OrderBy(x => x.AbsoluteTime)
                                 .FirstOrDefault();

                // If first lyric is placed at least a quarter note before first phrase, then add additional phrase before to fill gap
                if (!(firstLyric is null) && (firstPhrase.AbsoluteTime - firstLyric.AbsoluteTime) > DELTA_TICKS_PER_QUARTER)
                {
                    long start = firstLyric.AbsoluteTime;
                    long end   = firstPhrase.AbsoluteTime;

                    phraseEvents.Insert(0, new NoteEvent(start, DEFAULT_CHANNEL, MidiCommandCode.NoteOn, VOCALS_PHRASE, DEFAULT_VELOCITY));
                    phraseEvents.Insert(1, new NoteEvent(end, DEFAULT_CHANNEL, MidiCommandCode.NoteOff, VOCALS_PHRASE, DEFAULT_VELOCITY));
                }

                // Extends first phrase
                phraseEvents.First().AbsoluteTime = 0;

                // Extends last phrase
                phraseEvents.Last().AbsoluteTime = Math.Max(phraseEvents.Last().AbsoluteTime, track.Max(x => x.AbsoluteTime)) + DELTA_TICKS_PER_QUARTER / 8;
            }
            else
            {
                // No phrases found (Create one)
                phraseEvents.Add(new NoteEvent(0, 1, MidiCommandCode.NoteOn, VOCALS_PHRASE, 100));
                phraseEvents.Add(new NoteEvent(track.Max(x => x.AbsoluteTime) + DELTA_TICKS_PER_QUARTER / 8, 1, MidiCommandCode.NoteOff, VOCALS_PHRASE, 100));
            }

            // Shrinks phrases
            for (int i = 0; i < phraseEvents.Count; i += 2)
            {
                NoteEvent start = phraseEvents[i];
                NoteEvent end   = phraseEvents[i + 1];

                var betweenNotes = notes
                                   .Where(x => x.Start >= start.AbsoluteTime &&
                                          x.Start < end.AbsoluteTime)
                                   .OrderBy(x => x.Start) // Should already be sorted but you never know
                                   .ToList();

                if (betweenNotes.Count <= 0)
                {
                    continue; // No notes between phrases
                }
                var startNoteTime = betweenNotes
                                    .Select(x => x.Start)
                                    .First();

                var endNoteTime = betweenNotes
                                  .Select(x => x.End)
                                  .Last();

                // 1/128th note
                start.AbsoluteTime = startNoteTime - (DELTA_TICKS_PER_QUARTER / 32);
                end.AbsoluteTime   = endNoteTime + (DELTA_TICKS_PER_QUARTER / 32);

                track.Add(start);
                track.Add(end);
            }

            // Adds play event
            var firstNote = track.Where(x => x is NoteEvent).Select(y => y as NoteEvent).OrderBy(y => y.AbsoluteTime).FirstOrDefault();

            if (firstNote != null)
            {
                var idx = track.IndexOf(firstNote);
                track.Insert(idx, new NAudio.Midi.TextEvent("[play]", MetaEventType.TextEvent, firstNote.AbsoluteTime));
            }

            // Adds idle event (end)
            var lastNote = track.Where(x => x is NoteEvent).Select(y => y as NoteEvent).OrderByDescending(y => y.AbsoluteTime).FirstOrDefault();

            if (lastNote != null)
            {
                var idx = track.IndexOf(lastNote);
                track.Insert(idx, new NAudio.Midi.TextEvent("[idle]", MetaEventType.TextEvent, lastNote.AbsoluteTime));
            }

            // Sort by absolute time (And ensure track name is first event)
            track.Sort((x, y) => (int)(x is NAudio.Midi.TextEvent &&
                                       ((NAudio.Midi.TextEvent)x).MetaEventType == MetaEventType.SequenceTrackName
                                       ? int.MinValue : x.AbsoluteTime - y.AbsoluteTime));

            // Adds end track
            track.Add(new MetaEvent(MetaEventType.EndTrack, 0, track.Last().AbsoluteTime));
            return(track);
        }
Example #9
0
        public void Export(string path)
        {
            MidiEventCollection mid = new MidiEventCollection(1, DELTA_TICKS_PER_QUARTER);

            _xmks.Sort((x, y) => GetSortNumber(x.Name) - GetSortNumber(y.Name));

            int GetSortNumber(string name)
            {
                switch (name)
                {
                case "touchdrums":
                    return(1);

                case "touchguitar":
                    return(2);

                case "guitar_3x2":
                    return(3);

                case "vocals":
                    return(4);

                case "control":
                    return(5);

                default:
                    return(100);
                }
            }

            // Get xmk track to use for tempo map + generating BEAT track
            // Note: Avoiding "touchdrums" because it was an under-developed feature so it may not consistently be as accurate compared to 6 fret
            var tempoTrackPreference = new[] { "guitar_3x2", "touchguitar", "vocals", "control" };
            Xmk firstXmk             = _xmks
                                       .Select(x =>
            {
                var idx = Array.FindIndex(tempoTrackPreference, y => y == x.Name);
                if (idx == -1)
                {
                    idx = 100;
                }

                return(Xmk: x, Order: idx);
            })
                                       .OrderBy(x => x.Order)
                                       .Select(x => x.Xmk)
                                       .First();

            mid.AddTrack(CreateTempoTrack(firstXmk.TempoEntries, firstXmk.TimeSignatureEntries));

            if (!_exportOptions.Remap)
            {
                for (int i = 0; i < _xmks.Count; i++)
                {
                    mid.AddTrack(CreateTrack(_xmks[i], i));
                }
            }
            else
            {
                var eventsIdx = -1;
                for (int i = 0; i < _xmks.Count; i++)
                {
                    Xmk    xmk       = _xmks[i];
                    string trackName = !string.IsNullOrEmpty(xmk.Name) ? xmk.Name : $"NOTES {i}";

                    // Sets remapping and track name
                    switch (trackName.ToLower())
                    {
                    case "control":
                        // EVENTS
                        mid.AddTrack(ParseEvents(xmk));
                        eventsIdx = i + 1;
                        continue;

                    case "guitar_3x2":
                        // PART GUITAR GHL
                        mid.AddTrack(ParseGuitar3(xmk));
                        continue;

                    case "touchdrums":
                        // PART DRUMS
                        mid.AddTrack(ParseDrums(xmk));
                        continue;

                    case "touchguitar":
                        // PART GUITAR
                        mid.AddTrack(ParseGuitar5(xmk));
                        continue;

                    case "vocals":
                        // PART VOCALS
                        mid.AddTrack(ParseVocals(xmk));
                        continue;
                    }

                    // TODO: Refactor to be more elegant
                    if (_voxRegex.IsMatch(trackName))
                    {
                        mid.AddTrack(ParseVocals(xmk));
                        continue;
                    }

                    mid.AddTrack(CreateTrack(xmk, i));
                }

                // Updates EVENTS track
                var firstPlay = mid.SelectMany(x => x)
                                .Where(y => y is TextEvent && ((TextEvent)y).Text == "[play]")
                                .OrderBy(z => z.AbsoluteTime)
                                .FirstOrDefault();

                var lastIdle = mid.SelectMany(x => x)
                               .Where(y => y is TextEvent && ((TextEvent)y).Text == "[idle]")
                               .OrderByDescending(z => z.AbsoluteTime)
                               .FirstOrDefault();

                if (eventsIdx != -1 && firstPlay != null)
                {
                    var events    = mid[eventsIdx];
                    var nextEvent = events
                                    .OrderBy(y => y.AbsoluteTime)
                                    .FirstOrDefault(z => z.AbsoluteTime > firstPlay.AbsoluteTime);

                    // If nextEvent is null, assume no text events exist
                    var nextEventIdx = (nextEvent is null)
                        ? 1
                        : events.IndexOf(nextEvent);

                    events.Insert(nextEventIdx, new TextEvent("[music_start]", MetaEventType.TextEvent, firstPlay.AbsoluteTime));
                }

                if (eventsIdx != -1 && lastIdle != null)
                {
                    var events   = mid[eventsIdx];
                    var trackEnd = events.Last();
                    trackEnd.AbsoluteTime = lastIdle.AbsoluteTime; // Updates end track position
                    events.Remove(trackEnd);

                    events.Add(new TextEvent("[music_end]", MetaEventType.TextEvent, lastIdle.AbsoluteTime));
                    events.Add(new TextEvent("[end]", MetaEventType.TextEvent, lastIdle.AbsoluteTime));
                    events.Add(trackEnd); // Adds end track back
                }

                // Generates up/down events for BEAT track (Needed for beat markers in CH and OD in RB)
                mid.AddTrack(GenerateBeatTrack(firstXmk.TimeSignatureEntries, mid));
            }

            MidiFile.Export(path, mid);
        }
Example #10
0
        public static Xmk FromStream(Stream stream, string filePath = "")
        {
            AwesomeReader ar  = new AwesomeReader(stream, true);
            Xmk           xmk = new Xmk();

            xmk._filePath = filePath;

            // Reads header info
            xmk.Version = ar.ReadInt32();
            xmk.Hash    = ar.ReadInt32();

            int entryCount = ar.ReadInt32();
            int blobSize   = ar.ReadInt32();

            xmk.Unknown1 = ar.ReadUInt32();

            int tempoCount = ar.ReadInt32();
            int tsCount    = ar.ReadInt32();

            // Parses tempo map
            for (int i = 0; i < tempoCount; i++)
            {
                XmkTempo entry = new XmkTempo()
                {
                    Ticks           = ar.ReadUInt32(),
                    Start           = ar.ReadSingle(),
                    MicroPerQuarter = ar.ReadUInt32()
                };
                xmk.TempoEntries.Add(entry);
            }

            // Parse time signatures
            for (int i = 0; i < tsCount; i++)
            {
                XmkTimeSignature ts = new XmkTimeSignature()
                {
                    Ticks       = ar.ReadUInt32(),
                    Measure     = ar.ReadInt32(),
                    Numerator   = ar.ReadInt32(),
                    Denominator = ar.ReadInt32()
                };

                xmk.TimeSignatureEntries.Add(ts);
            }

            // Reads in strings
            long startOffset = ar.BaseStream.Position;

            ar.BaseStream.Seek(entryCount * 24, SeekOrigin.Current);
            xmk.StringBlob = ar.ReadBytes(blobSize);
            Dictionary <long, string> words = ParseBlob(xmk.StringBlob);

            ar.BaseStream.Seek(startOffset, SeekOrigin.Begin);

            // Parses events
            for (int i = 0; i < entryCount; i++)
            {
                XmkEvent entry = new XmkEvent()
                {
                    Unknown1 = ar.ReadUInt32(),
                    Unknown2 = ar.ReadUInt16(),
                    Unknown3 = ar.ReadByte(),
                    Pitch    = ar.ReadByte(),
                    Start    = ar.ReadSingle(),
                    End      = ar.ReadSingle(),
                    Unknown4 = ar.ReadUInt32()
                };

                // Adds text
                int offset = ar.ReadInt32() - (entryCount * 24);
                if (offset >= 0 && words.ContainsKey(offset))
                {
                    entry.Text = words[offset];
                }

                xmk.Entries.Add(entry);
            }

            return(xmk);
        }