public override void SaveAsMIDI(string fileName) { if (NumTracks == 0) { throw new InvalidDataException("Questa canzone non ha tracce."); } CalculateTicks(); var midi = new Sequence(96) { Format = 1 }; var metaTrack = new Sanford.Multimedia.Midi.Track(); midi.Add(metaTrack); for (int i = 0; i < NumTracks; i++) { var track = new Sanford.Multimedia.Midi.Track(); midi.Add(track); FreeNoteCommand freeNote = null; MidiEvent freeNoteOff = null; for (int j = 0; j < Commands[i].Count; j++) { var e = Commands[i][j]; // Extended note ended ended and wasn't renewed if (freeNoteOff != null && freeNoteOff.AbsoluteTicks < e.AbsoluteTicks * 2) { freeNote = null; freeNoteOff = null; } if (e.Command is VolumeCommand vol) { track.Insert(e.AbsoluteTicks * 2, new ChannelMessage(ChannelCommand.Controller, i, (int)ControllerType.Volume, vol.Volume / 2)); } else if (e.Command is VoiceCommand voice) { // TODO: Fork and remove restriction track.Insert(e.AbsoluteTicks * 2, new ChannelMessage(ChannelCommand.ProgramChange, i, voice.Voice & 0x7F)); } else if (e.Command is PanpotCommand pan) { track.Insert(e.AbsoluteTicks * 2, new ChannelMessage(ChannelCommand.Controller, i, (int)ControllerType.Pan, pan.Panpot / 2 + 0x40)); } else if (e.Command is BendCommand bend) { track.Insert(e.AbsoluteTicks * 2, new ChannelMessage(ChannelCommand.PitchWheel, i, 0, bend.Bend / 2 + 0x40)); } else if (e.Command is BendRangeCommand bendr) { track.Insert(e.AbsoluteTicks * 2, new ChannelMessage(ChannelCommand.Controller, i, 20, bendr.Range / 2)); } else if (e.Command is MLSSNoteCommand note) { // Extended note is playing and it should be extended by this note if (freeNote != null && freeNote.Note - 0x80 == note.Note) { // Move the note off command track.Move(freeNoteOff, freeNoteOff.AbsoluteTicks + note.Duration * 2); } // Extended note is playing but this note is different OR there is no extended note playing // Either way we play a new note and forget that one else { track.Insert(e.AbsoluteTicks * 2, new ChannelMessage(ChannelCommand.NoteOn, i, note.Note, 0x7F)); track.Insert(e.AbsoluteTicks * 2 + note.Duration * 2, new ChannelMessage(ChannelCommand.NoteOff, i, note.Note)); freeNote = null; freeNoteOff = null; } } else if (e.Command is FreeNoteCommand free) { // Extended note is playing and it should be extended if (freeNote != null && freeNote.Note == free.Note) { // Move the note off command track.Move(freeNoteOff, freeNoteOff.AbsoluteTicks + free.Duration * 2); } // Extended note is playing but this note is different OR there is no extended note playing // Either way we play a new note and forget that one else { track.Insert(e.AbsoluteTicks * 2, new ChannelMessage(ChannelCommand.NoteOn, i, free.Note - 0x80, 0x7F)); track.Insert(e.AbsoluteTicks * 2 + free.Duration * 2, new ChannelMessage(ChannelCommand.NoteOff, i, free.Note - 0x80)); freeNote = free; freeNoteOff = track.GetMidiEvent(track.Count - 2); // -1 would be the end of track event } } else if (i == 0 && e.Command is TempoCommand tempo) { var change = new TempoChangeBuilder { Tempo = (60000000 / tempo.Tempo) }; change.Build(); metaTrack.Insert(e.AbsoluteTicks * 2, change.Result); } else if (i == 0 && e.Command is GoToCommand goTo) { int jumpCmd = Commands[i].FindIndex(c => c.GetOffset() == goTo.Offset); metaTrack.Insert(Commands[i][jumpCmd].AbsoluteTicks * 2, new MetaMessage(MetaType.Marker, new byte[] { (byte)'[' })); metaTrack.Insert(e.AbsoluteTicks * 2, new MetaMessage(MetaType.Marker, new byte[] { (byte)']' })); } else if (e.Command is FinishCommand fine) { // TODO: Fix ticks before end of track event // Library automatically is updating track.EndOfTrackOffset for us break; } } } midi.Save(fileName); }
public MLSSSong(int offset) { SetOffset(offset); VoiceTable = VoiceTable.LoadTable <MLSSVoiceTable>(0, true); // 0 won't be used in the Load method int amt = GetTrackAmount(ROM.Instance.Reader.ReadInt16(offset)); Commands = new List <SongEvent> [amt]; for (int i = 0; i < amt; i++) { Commands[i] = new List <SongEvent>(); int track = offset + ROM.Instance.Reader.ReadInt16(offset + 2 + (i * 2)); ROM.Instance.Reader.BaseStream.Position = track; byte cmd = 0; while (cmd != 0xFF && cmd != 0xF8) { int off = (int)ROM.Instance.Reader.BaseStream.Position; ICommand command = null; cmd = ROM.Instance.Reader.ReadByte(); switch (cmd) { case 0: command = new FreeNoteCommand { Note = ROM.Instance.Reader.ReadByte(), Duration = ROM.Instance.Reader.ReadByte() }; break; case 0xF0: command = new VoiceCommand { Voice = ROM.Instance.Reader.ReadByte() }; break; case 0xF1: command = new VolumeCommand { Volume = ROM.Instance.Reader.ReadByte() }; break; case 0xF2: command = new PanpotCommand { Panpot = (sbyte)(ROM.Instance.Reader.ReadByte() - 0x80) }; break; case 0xF4: command = new BendRangeCommand { Range = ROM.Instance.Reader.ReadByte() }; break; case 0xF5: command = new BendCommand { Bend = ROM.Instance.Reader.ReadSByte() }; break; case 0xF6: command = new RestCommand { Rest = ROM.Instance.Reader.ReadByte() }; break; case 0xF8: short offsetFromEnd = ROM.Instance.Reader.ReadInt16(); command = new GoToCommand { Offset = (int)(ROM.Instance.Reader.BaseStream.Position + offsetFromEnd) }; break; case 0xF9: command = new TempoCommand { Tempo = ROM.Instance.Reader.ReadByte() }; break; case 0xFF: command = new FinishCommand(); break; default: command = new MLSSNoteCommand { Duration = cmd, Note = ROM.Instance.Reader.ReadSByte() }; break; } Commands[i].Add(new SongEvent(off, command)); } } }