private void OnMetaMessagePlayed(object sender, MetaMessageEventArgs e) { if (e.Message.MetaType == MetaType.Tempo) { TempoChangeBuilder builder = new TempoChangeBuilder(e.Message); midiTempo = (60000000 / builder.Tempo); OnTempoChange?.Invoke(this, midiTempo); } if (e.Message.MetaType == MetaType.Lyric) { MetaTextBuilder builder = new MetaTextBuilder(e.Message); if (e.MidiTrack == LoadedTrack) { OnLyric?.Invoke(this, builder.Text); } } if (e.Message.MetaType == MetaType.TrackName) { MetaTextBuilder builder = new MetaTextBuilder(e.Message); ParseTrackName(e.MidiTrack, builder.Text); if (e.MidiTrack == LoadedTrack) { OnTrackNameChange?.Invoke(this, builder.Text); } } if (e.Message.MetaType == MetaType.InstrumentName) { MetaTextBuilder builder = new MetaTextBuilder(e.Message); OnTrackNameChange?.Invoke(this, builder.Text); Console.WriteLine("Instrument name: " + builder.Text); } }
public void AddTrack(string TrackName, List <TLBaseKeyFrame> KeyFrames) { Track t = new Track(); MetaTextBuilder mtBuilder = new MetaTextBuilder(MetaType.TrackName, TrackName); mtBuilder.Build(); t.Insert(0, mtBuilder.Result); foreach (TLMidiKeyFrame mk in KeyFrames) { ChannelMessageBuilder cmBuilder = new ChannelMessageBuilder(); cmBuilder.Command = ChannelCommand.NoteOn; cmBuilder.MidiChannel = mk.Channel; cmBuilder.Data1 = mk.Note; cmBuilder.Data2 = mk.Velocity; cmBuilder.Build(); t.Insert((int)(mk.Time / SecondsPerMidiUnit), cmBuilder.Result); cmBuilder.Command = ChannelCommand.NoteOff; cmBuilder.Data1 = mk.Note; cmBuilder.Data2 = 0; cmBuilder.Build(); t.Insert((int)(mk.End / SecondsPerMidiUnit), cmBuilder.Result); } FSequence.Add(t); }
public override void CreateEvents() { props.SetUpdatedEventPair(Owner.Insert(DownTick, MetaTextBuilder.Create(MetaType.Text, Text))); if (IsNew) { AddToList(); } }
private Sequence CreateSequence() { if (tunes == null || tunes.Count < 2) { return(null); } selectedTune = 1; SetDefaultValues(); nextNote = TimeSpan.Zero; SetHeaderValues(); SetHeaderValues(selectedTune, true); StartMeasure(); sequence = new Sequence(Ppqn); sequence.Format = 1; metaTrack = new Track(); mainTrack = new Track(); sequence.Add(metaTrack); TempoChangeBuilder tempoBuilder = new TempoChangeBuilder(); tempoBuilder.Tempo = Tempo; tempoBuilder.Build(); metaTrack.Insert(0, tempoBuilder.Result); TimeSignatureBuilder timeBuilder = new TimeSignatureBuilder(); timeBuilder.Numerator = timeSigNumerator; timeBuilder.Denominator = timeSigDenominator; timeBuilder.Build(); metaTrack.Insert(0, timeBuilder.Result); sequence.Add(mainTrack); MetaTextBuilder textBuilder = new MetaTextBuilder(); textBuilder.Type = MetaType.TrackName; textBuilder.Text = "Tempo Track"; textBuilder.Build(); metaTrack.Insert(0, textBuilder.Result); textBuilder = new MetaTextBuilder(); textBuilder.Type = MetaType.TrackName; textBuilder.Text = "Tune 1"; textBuilder.Build(); mainTrack.Insert(0, textBuilder.Result); while (tokenIndex < tokens.Count) { AddNextNote(); } return(sequence); }
private void ParseMetaMessage() { if (trackIndex >= trackData.Length) { throw new MidiFileException("End of track unexpectedly reached."); } MetaType type = (MetaType)trackData[trackIndex]; trackIndex++; if (trackIndex >= trackData.Length) { throw new MidiFileException("End of track unexpectedly reached."); } if (type == MetaType.EndOfTrack) { newTrack.EndOfTrackOffset = ticks - previousTicks; trackIndex++; } else { byte[] data = new byte[ReadVariableLengthValue()]; Array.Copy(trackData, trackIndex, data, 0, data.Length); MetaMessage metaMsg = new MetaMessage(type, data); // Record the time signature information if (type == MetaType.TimeSignature) { TimeSignatureBuilder timeSignature = new TimeSignatureBuilder(metaMsg); timeSignatureNumerator = timeSignature.Numerator; timeSignatureDenominator = timeSignature.Denominator; } // Record the track name else if (type == MetaType.TrackName) { MetaTextBuilder trackName = new MetaTextBuilder(metaMsg); newTrack.Name = trackName.Text; } newTrack.Insert(ticks, new MetaMessage(type, data)); trackIndex += data.Length; } }
public void Load(string file, int trackNum = 0) { Sequence = new Sequence(); OnTrackNameChange?.Invoke(this, string.Empty); OnTempoChange?.Invoke(this, 0); if (!File.Exists(file)) { throw new FileNotFoundException("Midi file does not exist."); } loadedError = string.Empty; try { Sequence = new Sequence(file); } catch (Exception e) { throw e; } if (trackNum >= Sequence.Count) { trackNum = Sequence.Count - 1; } loadedFilename = file; intendedTrack = trackNum; preferredInstruments.Clear(); preferredOctaveShift.Clear(); // Collect statistics notesPlayedCount.Clear(); foreach (Track track in Sequence) { notesPlayedCount[track] = 0; foreach (MidiEvent ev in track.Iterator()) { if (ev.MidiMessage is ChannelMessage chanMsg) { if (chanMsg.Command == ChannelCommand.NoteOn) { if (chanMsg.Data2 > 0) { notesPlayedCount[track]++; } } } } } // Count notes and select fìrst that actually has stuff if (trackNum == 0) { while (trackNum < Sequence.Count) { int tnotes = 0; foreach (MidiEvent ev in Sequence[trackNum].Iterator()) { if (intendedTrack == 0) { if (ev.MidiMessage is ChannelMessage chanMsg) { if (chanMsg.Command == ChannelCommand.NoteOn) { tnotes++; } } if (ev.MidiMessage is MetaMessage metaMsg) { if (metaMsg.MetaType == MetaType.Lyric) { tnotes++; } } } } if (tnotes == 0) { trackNum++; } else { break; } } if (trackNum == Sequence.Count) { Console.WriteLine("No playable track..."); trackNum = intendedTrack; } } // Show initial tempo foreach (MidiEvent ev in Sequence[0].Iterator()) { if (ev.AbsoluteTicks == 0) { if (ev.MidiMessage is MetaMessage metaMsg) { if (metaMsg.MetaType == MetaType.Tempo) { OnMetaMessagePlayed(this, new MetaMessageEventArgs(Sequence[0], metaMsg)); } } } } // Parse track names and octave shifts foreach (Track track in Sequence) { foreach (MidiEvent ev in track.Iterator()) { if (ev.MidiMessage is MetaMessage metaMsg) { if (metaMsg.MetaType == MetaType.TrackName) { MetaTextBuilder builder = new MetaTextBuilder(metaMsg); this.ParseTrackName(track, builder.Text); } } } } loadedTrack = trackNum; lyricCount = 0; // Search beginning for text stuff foreach (MidiEvent ev in LoadedTrack.Iterator()) { if (ev.MidiMessage is MetaMessage msg) { if (msg.MetaType == MetaType.TrackName) { OnMetaMessagePlayed(this, new MetaMessageEventArgs(LoadedTrack, msg)); } if (msg.MetaType == MetaType.Lyric) { lyricCount++; } } if (ev.MidiMessage is ChannelMessage chanMsg) { if (chanMsg.Command == ChannelCommand.ProgramChange) { OnSimpleChannelMessagePlayed(this, new ChannelMessageEventArgs(Sequence[0], chanMsg)); } } } OnLoad?.Invoke(this, EventArgs.Empty); Console.WriteLine("Loaded midi [" + file + "] track " + trackNum); }
/// <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); }