public static void ExportMsb(Stream dataStream, string outPath) { var file = MsbFile.Read(dataStream); var midiFile = new MidiFile(); var tempoMap = TempoMap.Create(new TicksPerQuarterNoteTimeDivision(960), Tempo.FromBeatsPerMinute((int)file.BpmEntries[0].Bpm)); foreach (var score in file.ScoreEntries) { var track = new TrackChunk(); var scoreNotes = new List <Note>(); foreach (var bar in score.Bars) { scoreNotes.Add(new Note(new SevenBitNumber((byte)(bar.Note + 24)), bar.Length, bar.Offset)); } track.AddNotes(scoreNotes); midiFile.Chunks.Add(track); } midiFile.ReplaceTempoMap(tempoMap); midiFile.Write(outPath); }
public static byte[] CreateMsbFromMidi(string path) { var midiFile = MidiFile.Read(path); //Ensures every chunk in the midi has at least 1 note foreach (var chunk in midiFile.GetTrackChunks()) { if (chunk.GetNotes().Count() == 0) { List <Note> dummyTrack = new List <Note>(); dummyTrack.Add(new Note(new SevenBitNumber((byte)60), 1000, 1)); chunk.AddNotes(dummyTrack); } } //Ensures there are at least 7 chunks in the midi while (midiFile.Chunks.Count < 7) { TrackChunk chunk = new TrackChunk(); List <Note> dummyTrack = new List <Note>(); dummyTrack.Add(new Note(new SevenBitNumber((byte)60), 1000, 1)); chunk.AddNotes(dummyTrack); midiFile.Chunks.Add(chunk); } //Ensures there are no more than 7 chunks in the midi while (midiFile.GetTrackChunks().Count() > 7) { midiFile.Chunks.RemoveAt(7); } //Reads BPM from midi if possible; if no BPM is declared, defaults to 100 ValueChange <Tempo> tempo = midiFile.GetTempoMap().Tempo.FirstOrDefault <ValueChange <Tempo> >(); float bpm = 100; if (tempo != null) { bpm = (float)tempo.Value.BeatsPerMinute; } //Assigns time signature based on first midi time signature event, or defaults to 4/4 ValueChange <TimeSignature> ts = midiFile.GetTempoMap().TimeSignature.FirstOrDefault <ValueChange <TimeSignature> >(); int numerator = 4; int denominator = 4; if (ts != null) { numerator = ts.Value.Numerator; denominator = ts.Value.Denominator; } var file = new MsbFile { BpmEntries = new List <MsbFile.MsbBpm> { new MsbFile.MsbBpm { Bpm = bpm, Measure1 = (byte)numerator, Measure2 = (byte)denominator } }, ScoreEntries = new List <MsbFile.MsbScore>() }; var trackChunks = midiFile.GetTrackChunks(); //If the first chunk contains only one note (dummy chunk or empty chunk) //Instead, re-order list to sort the list with the most notes into the first slot. if (trackChunks.ElementAt(0).GetNotes().Count() == 1) { trackChunks = trackChunks.OrderByDescending(c => c.GetNotes().Count()); } for (var trackIndex = 0; trackIndex < trackChunks.Count(); trackIndex++) { var notes = trackChunks.ElementAt(trackIndex).GetNotes(); notes = notes.OrderBy(c => c.Time); var msbScore = new MsbFile.MsbScore { Bars = new List <MsbFile.MsbBar>() }; for (var i = 0; i < notes.Count(); i++) { var note = notes.ElementAt(i); if ((int)note.NoteNumber < 48 || (int)note.NoteNumber > 84) { throw new Exception("Your Midi contains notes outside the range of FFXIV's playable range. " + $"Please enter only notes between C3 and C6 (inclusive). Detected incorrect range at Note#{i + 1} in Track#{trackIndex + 1}"); } //Detects note overlaps and chops the length to be strictly < the next note's time. var lengthChopOffset = 0L; if (i != notes.Count() - 1) { var nextNote = notes.ElementAt(i + 1); if (note.Time + note.Length > nextNote.Time) { lengthChopOffset = (note.Time + note.Length) - nextNote.Time; } } var msbBar = new MsbFile.MsbBar(); msbBar.Note = (byte)(note.NoteNumber - 24); msbBar.Length = (uint)(note.Length - lengthChopOffset); msbBar.Offset = (uint)note.Time; msbScore.Bars.Add(msbBar); } file.ScoreEntries.Add(msbScore); } return(file.GetBytes()); }
public static MsbFile Read(Stream stream) { var file = new MsbFile(); file.ScoreEntries = new List <MsbScore>(); using (var reader = new BinaryReader(stream)) { if (reader.ReadInt32() != 0x4642534D) // MSBF { throw new ArgumentException("Not a MSB file", nameof(stream)); } stream.Position++; //? var scoreCount = reader.ReadByte(); var headerSize = reader.ReadUInt16(); var musicLength = reader.ReadUInt32(); stream.Position += 4; var extendedHeaderSize = reader.ReadUInt32(); stream.Position = extendedHeaderSize; // Extended header, maybe instruments for ensembles? // BPM HEADER stream.Position += 4; var bpmHeaderCount = reader.ReadUInt32(); stream.Position += 8; file.BpmEntries = new List <MsbBpm>(); for (var bpmIndex = 0; bpmIndex < bpmHeaderCount; bpmIndex++) { var bpm = new MsbBpm(); stream.Position++; //? bpm.Measure1 = reader.ReadByte(); bpm.Measure2 = reader.ReadByte(); stream.Position++; bpm.Bpm = reader.ReadSingle(); reader.ReadUInt32(); // Unknown reader.ReadUInt32(); file.BpmEntries.Add(bpm); } for (var i = 0; i < scoreCount; i++) { var score = new MsbScore(); score.Bars = new List <MsbBar>(); stream.Position += 4; var noteCount = reader.ReadUInt32(); stream.Position += 8; for (var noteIndex = 0; noteIndex < noteCount; noteIndex++) { var bar = new MsbBar(); stream.Position++; //? bar.Note = reader.ReadByte(); stream.Position += 2; //? bar.Offset = reader.ReadUInt32(); bar.Length = reader.ReadUInt32(); reader.ReadUInt32(); // Unknown score.Bars.Add(bar); } file.ScoreEntries.Add(score); } } return(file); }