static byte[] GetInstrumentBytes(Song song, Song.Instrument instrument, ExportOptions exportOptions, float resolutionScaleRatio) { // Collect all bytes from each difficulty of the instrument, assigning the position for each event unsorted //List<SortableBytes> byteEvents = new List<SortableBytes>(); SortableBytes[] easyBytes = GetChartSortableBytes(song, instrument, Song.Difficulty.Easy, exportOptions); SortableBytes[] mediumBytes = GetChartSortableBytes(song, instrument, Song.Difficulty.Medium, exportOptions); SortableBytes[] hardBytes = GetChartSortableBytes(song, instrument, Song.Difficulty.Hard, exportOptions); SortableBytes[] expertBytes = GetChartSortableBytes(song, instrument, Song.Difficulty.Expert, exportOptions); SortableBytes[] em = SortableBytes.MergeAlreadySorted(easyBytes, mediumBytes); SortableBytes[] he = SortableBytes.MergeAlreadySorted(hardBytes, expertBytes); List <SortableBytes> sortedEvents = new List <SortableBytes>(SortableBytes.MergeAlreadySorted(em, he)); // Perform merge sort to re-order everything correctly //SortableBytes[] sortedEvents = new SortableBytes[easyBytes.Length + mediumBytes.Length + hardBytes.Length + expertBytes.Length];//byteEvents.ToArray(); //SortableBytes.Sort(sortedEvents); // Strip out duplicate events. This may occur with cymbal flags across multiple difficulties for (int i = sortedEvents.Count - 1; i >= 0; --i) { int next = i + 1; while (next < sortedEvents.Count && sortedEvents[i].tick == sortedEvents[next].tick) { if (sortedEvents[i].bytes.SequenceEqual(sortedEvents[next].bytes)) { sortedEvents.RemoveAt(next); } ++next; } } return(SortableBytesToTimedEventBytes(sortedEvents.ToArray(), song, exportOptions, resolutionScaleRatio)); }
static void InsertionSort(IList <SortableBytes> eventList, SortableBytes sortableByte) { int index = eventList.Count - 1; while (index >= 0 && sortableByte.tick < eventList[index].tick) { --index; } eventList.Insert(index + 1, sortableByte); }
static byte[] GetUnrecognisedChartBytes(Chart chart, ExportOptions exportOptions, float resolutionScaleRatio) { List <SortableBytes> eventList = new List <SortableBytes>(); foreach (ChartObject chartObject in chart.chartObjects) { SortableBytes onEvent = null; SortableBytes offEvent = null; Note note = chartObject as Note; if (note != null) { GetUnrecognisedChartNoteBytes(note, out onEvent, out offEvent); } Starpower sp = chartObject as Starpower; if (sp != null) // Starpower cannot be split up between charts in a midi file { GetStarpowerBytes(sp, out onEvent, out offEvent); } ChartEvent chartEvent = chartObject as ChartEvent; if (chartEvent != null) // Text events cannot be split up in the file { SortableBytes bytes = GetChartEventBytes(chartEvent); InsertionSort(eventList, bytes); } if (onEvent != null && offEvent != null) { InsertionSort(eventList, onEvent); if (offEvent.tick == onEvent.tick) { ++offEvent.tick; } InsertionSort(eventList, offEvent); } } return(SortableBytesToTimedEventBytes(eventList.ToArray(), chart.song, exportOptions, resolutionScaleRatio)); }
static void GetNoteNumberBytes(int noteNumber, Note note, out SortableBytes onEvent, out SortableBytes offEvent) { onEvent = new SortableBytes(note.tick, new byte[] { ON_EVENT, (byte)noteNumber, VELOCITY }); offEvent = new SortableBytes(note.tick + note.length, new byte[] { OFF_EVENT, (byte)noteNumber, VELOCITY }); }
static void GetUnrecognisedChartNoteBytes(Note note, out SortableBytes onEvent, out SortableBytes offEvent) { GetNoteNumberBytes(note.rawNote, note, out onEvent, out offEvent); }
static void GetSoloBytes(ChartEvent solo, uint soloEndTick, out SortableBytes onEvent, out SortableBytes offEvent) { onEvent = new SortableBytes(solo.tick, new byte[] { ON_EVENT, MidIOHelper.SOLO_NOTE, VELOCITY }); offEvent = new SortableBytes(soloEndTick, new byte[] { OFF_EVENT, MidIOHelper.SOLO_NOTE, VELOCITY }); }
/* CHART EVENT BYTE DETERMINING ***********************************************************************************************/ static void GetStarpowerBytes(Starpower sp, out SortableBytes onEvent, out SortableBytes offEvent) { onEvent = new SortableBytes(sp.tick, new byte[] { ON_EVENT, MidIOHelper.STARPOWER_NOTE, VELOCITY }); offEvent = new SortableBytes(sp.tick + sp.length, new byte[] { OFF_EVENT, MidIOHelper.STARPOWER_NOTE, VELOCITY }); }
static SortableBytes[] GetChartSortableBytes(Song song, Song.Instrument instrument, Song.Difficulty difficulty, ExportOptions exportOptions) { Chart chart = song.GetChart(instrument, difficulty); Chart.GameMode gameMode = chart.gameMode; if (exportOptions.copyDownEmptyDifficulty) { Song.Difficulty chartDiff = difficulty; while (chart.notes.Count <= 0) { switch (chartDiff) { case (Song.Difficulty.Easy): chartDiff = Song.Difficulty.Medium; break; case (Song.Difficulty.Medium): chartDiff = Song.Difficulty.Hard; break; case (Song.Difficulty.Hard): chartDiff = Song.Difficulty.Expert; break; case (Song.Difficulty.Expert): default: return(new SortableBytes[0]); } chart = song.GetChart(instrument, chartDiff); } } List <SortableBytes> eventList = new List <SortableBytes>(); ChartEvent soloOnEvent = null; foreach (ChartObject chartObject in chart.chartObjects) { Note note = chartObject as Note; SortableBytes onEvent = null; SortableBytes offEvent = null; if (note != null) { int noteNumber = GetMidiNoteNumber(note, gameMode, difficulty); GetNoteNumberBytes(noteNumber, note, out onEvent, out offEvent); if (exportOptions.forced) { // Forced notes if ((note.flags & Note.Flags.Forced) != 0 && note.type != Note.NoteType.Tap && (note.previous == null || (note.previous.tick != note.tick))) // Don't overlap on chords { // Add a note int difficultyNumber; int forcingOffset; if (!c_difficultyToMidiNoteWriteDict.TryGetValue(difficulty, out difficultyNumber)) { throw new Exception("Unhandled difficulty"); } if (!c_forcingMidiWriteOffsets.TryGetValue(note.type, out forcingOffset)) { throw new Exception("Unhandled note type found when trying to write forcing flag"); } int forcedNoteNumber = difficultyNumber + forcingOffset; SortableBytes forceOnEvent = new SortableBytes(note.tick, new byte[] { ON_EVENT, (byte)forcedNoteNumber, VELOCITY }); SortableBytes forceOffEvent = new SortableBytes(note.tick + 1, new byte[] { OFF_EVENT, (byte)forcedNoteNumber, VELOCITY }); InsertionSort(eventList, forceOnEvent); InsertionSort(eventList, forceOffEvent); } if (instrument == Song.Instrument.Drums && ((note.flags & Note.Flags.ProDrums_Cymbal) == 0)) // We want to write our flags if the cymbal is toggled OFF, as these notes are cymbals by default { int tomToggleNoteNumber; if (MidIOHelper.PAD_TO_CYMBAL_LOOKUP.TryGetValue(note.drumPad, out tomToggleNoteNumber)) { SortableBytes tomToggleOnEvent = new SortableBytes(note.tick, new byte[] { ON_EVENT, (byte)tomToggleNoteNumber, VELOCITY }); SortableBytes tomToggleOffEvent = new SortableBytes(note.tick + 1, new byte[] { OFF_EVENT, (byte)tomToggleNoteNumber, VELOCITY }); InsertionSort(eventList, tomToggleOnEvent); InsertionSort(eventList, tomToggleOffEvent); } } int openNote = gameMode == Chart.GameMode.GHLGuitar ? (int)Note.GHLiveGuitarFret.Open : (int)Note.GuitarFret.Open; // Add tap sysex events if (difficulty == Song.Difficulty.Expert && note.rawNote != openNote && (note.flags & Note.Flags.Tap) != 0 && (note.previous == null || (note.previous.flags & Note.Flags.Tap) == 0)) // This note is a tap while the previous one isn't as we're creating a range { // Find the next non-tap note Note nextNonTap = note; while (nextNonTap.next != null && nextNonTap.rawNote != openNote && (nextNonTap.next.flags & Note.Flags.Tap) != 0) { nextNonTap = nextNonTap.next; } // Tap event = 08-50-53-00-00-FF-04-01, end with 01 for On, 00 for Off byte[] tapOnEventBytes = new byte[] { SYSEX_START, 0x08, 0x50, 0x53, 0x00, 0x00, 0xFF, 0x04, SYSEX_ON, SYSEX_END }; byte[] tapOffEventBytes = new byte[] { SYSEX_START, 0x08, 0x50, 0x53, 0x00, 0x00, 0xFF, 0x04, SYSEX_OFF, SYSEX_END }; SortableBytes tapOnEvent = new SortableBytes(note.tick, tapOnEventBytes); SortableBytes tapOffEvent = new SortableBytes(nextNonTap.tick + 1, tapOffEventBytes); InsertionSort(eventList, tapOnEvent); InsertionSort(eventList, tapOffEvent); } } if (gameMode != Chart.GameMode.Drums && gameMode != Chart.GameMode.GHLGuitar && difficulty == Song.Difficulty.Expert && note.guitarFret == Note.GuitarFret.Open && (note.previous == null || (note.previous.guitarFret != Note.GuitarFret.Open))) { // Find the next non-open note Note nextNonOpen = note; while (nextNonOpen.next != null && nextNonOpen.next.guitarFret == Note.GuitarFret.Open) { nextNonOpen = nextNonOpen.next; } byte diff; switch (difficulty) { case (Song.Difficulty.Easy): diff = 0; break; case (Song.Difficulty.Medium): diff = 1; break; case (Song.Difficulty.Hard): diff = 2; break; case (Song.Difficulty.Expert): diff = 3; break; default: continue; } byte[] openOnEventBytes = new byte[] { SYSEX_START, 0x08, 0x50, 0x53, 0x00, 0x00, diff, 0x01, SYSEX_ON, SYSEX_END }; byte[] openOffEventBytes = new byte[] { SYSEX_START, 0x08, 0x50, 0x53, 0x00, 0x00, diff, 0x01, SYSEX_OFF, SYSEX_END }; SortableBytes openOnEvent = new SortableBytes(note.tick, openOnEventBytes); SortableBytes openOffEvent = new SortableBytes(nextNonOpen.tick + 1, openOffEventBytes); InsertionSort(eventList, openOnEvent); InsertionSort(eventList, openOffEvent); } } Starpower sp = chartObject as Starpower; if (sp != null && difficulty == Song.Difficulty.Expert) // Starpower cannot be split up between charts in a midi file { GetStarpowerBytes(sp, out onEvent, out offEvent); } ChartEvent chartEvent = chartObject as ChartEvent; if (chartEvent != null && difficulty == Song.Difficulty.Expert) // Text events cannot be split up in the file { if (soloOnEvent != null && chartEvent.eventName == MidIOHelper.SoloEndEventText) { GetSoloBytes(soloOnEvent, chartEvent.tick, out onEvent, out offEvent); soloOnEvent = null; } else if (chartEvent.eventName == MidIOHelper.SoloEventText) { soloOnEvent = chartEvent; } else { InsertionSort(eventList, GetChartEventBytes(chartEvent)); } } if (onEvent != null && offEvent != null) { InsertionSort(eventList, onEvent); if (offEvent.tick == onEvent.tick) { ++offEvent.tick; } InsertionSort(eventList, offEvent); } } if (soloOnEvent != null) // Found a solo event with no end. Assume the solo lasts for the rest of the song { SortableBytes onEvent = null; SortableBytes offEvent = null; uint soloEndTick = chart.chartObjects[chart.chartObjects.Count - 1].tick; // In order to get a solo event the chart objects needed to have some object in this container, no need to check size, hopefully... GetSoloBytes(soloOnEvent, soloEndTick, out onEvent, out offEvent); if (onEvent != null && offEvent != null) { InsertionSort(eventList, onEvent); if (offEvent.tick == onEvent.tick) { ++offEvent.tick; } InsertionSort(eventList, offEvent); } } return(eventList.ToArray()); }