protected void Load(byte[] binary, EndianBinaryReader reader, int headerOffset) { _binary = binary; Header = reader.ReadObject <M4ASongHeader>(headerOffset); VoiceTable = VoiceTable.LoadTable <M4AVoiceTable>(Header.VoiceTable - ROM.Pak); Commands = new List <SongEvent> [Header.NumTracks]; for (int i = 0; i < Header.NumTracks; i++) { Commands[i] = new List <SongEvent>(); } if (Header.NumTracks > ROM.Instance.Game.Engine.TrackLimit) { throw new InvalidDataException($"La canzone ha troppe tracce ({Header.NumTracks})."); } for (int i = 0; i < NumTracks; i++) { reader.BaseStream.Position = Header.Tracks[i] - ROM.Pak; byte cmd = 0, runCmd = 0, prevNote = 0, prevVelocity = 0x7F; while (cmd != 0xB1 && cmd != 0xB6) { int off = (int)reader.BaseStream.Position; ICommand command = null; cmd = reader.ReadByte(); if (cmd >= 0xBD) // Commands that work within running status { runCmd = cmd; } #region TIE & Notes if (runCmd >= 0xCF && cmd < 0x80) // Within running status { var peek = reader.PeekBytes(2); if (peek[0] >= 0x80) { command = AddNoteEvent(cmd, prevVelocity, 0, runCmd, out prevNote, out prevVelocity); } else if (peek[1] > 3 || peek[1] < 1) { command = AddNoteEvent(cmd, reader.ReadByte(), 0, runCmd, out prevNote, out prevVelocity); } else { command = AddNoteEvent(cmd, reader.ReadByte(), reader.ReadByte(), runCmd, out prevNote, out prevVelocity); } } else if (cmd >= 0xCF) { var peek = reader.PeekBytes(3); if (peek[0] >= 0x80) { command = AddNoteEvent(prevNote, prevVelocity, 0, runCmd, out prevNote, out prevVelocity); } else if (peek[1] >= 0x80) { command = AddNoteEvent(reader.ReadByte(), prevVelocity, 0, runCmd, out prevNote, out prevVelocity); } // TIE cannot have an added duration so it needs to stop here else if (cmd == 0xCF || peek[2] > 3 || peek[2] < 1) { command = AddNoteEvent(reader.ReadByte(), reader.ReadByte(), 0, runCmd, out prevNote, out prevVelocity); } else { command = AddNoteEvent(reader.ReadByte(), reader.ReadByte(), reader.ReadByte(), runCmd, out prevNote, out prevVelocity); } } #endregion #region Rests else if (cmd >= 0x80 && cmd <= 0xB0) { command = new RestCommand { Rest = SongEvent.RestFromCMD(0x80, cmd) } } ; #endregion #region Commands else if (runCmd < 0xCF && cmd < 0x80) // Commands within running status { switch (runCmd) { case 0xBD: command = new VoiceCommand { Voice = cmd }; break; case 0xBE: command = new VolumeCommand { Volume = cmd }; break; case 0xBF: command = new PanpotCommand { Panpot = (sbyte)(cmd - 0x40) }; break; case 0xC0: command = new BendCommand { Bend = (sbyte)(cmd - 0x40) }; break; case 0xC1: command = new BendRangeCommand { Range = cmd }; break; case 0xC2: command = new LFOSpeedCommand { Speed = cmd }; break; case 0xC3: command = new LFODelayCommand { Delay = cmd }; break; case 0xC4: command = new ModDepthCommand { Depth = cmd }; break; case 0xC5: command = new ModTypeCommand { Type = cmd }; break; case 0xC8: command = new TuneCommand { Tune = (sbyte)(cmd - 0x40) }; break; case 0xCD: command = new LibraryCommand { Command = cmd, Argument = reader.ReadByte() }; break; case 0xCE: command = new EndOfTieCommand { Note = (sbyte)cmd }; prevNote = cmd; break; } } else if (cmd > 0xB0 && cmd < 0xCF) { switch (cmd) { case 0xB1: // FINE & PREV case 0xB6: command = new M4AFinishCommand { Type = cmd }; break; case 0xB2: command = new GoToCommand { Offset = reader.ReadInt32() - ROM.Pak }; break; case 0xB3: command = new CallCommand { Offset = reader.ReadInt32() - ROM.Pak }; break; case 0xB4: command = new ReturnCommand(); break; case 0xB5: command = new RepeatCommand { Times = reader.ReadByte(), Offset = reader.ReadInt32() - ROM.Pak }; break; case 0xB9: command = new MemoryAccessCommand { Arg1 = reader.ReadByte(), Arg2 = reader.ReadByte(), Arg3 = reader.ReadByte() }; break; case 0xBA: command = new PriorityCommand { Priority = reader.ReadByte() }; break; case 0xBB: command = new TempoCommand { Tempo = (short)(reader.ReadByte() * 2) }; break; case 0xBC: command = new KeyShiftCommand { Shift = reader.ReadSByte() }; break; // Commands that work within running status: case 0xBD: command = new VoiceCommand { Voice = reader.ReadByte() }; break; case 0xBE: command = new VolumeCommand { Volume = reader.ReadByte() }; break; case 0xBF: command = new PanpotCommand { Panpot = (sbyte)(reader.ReadByte() - 0x40) }; break; case 0xC0: command = new BendCommand { Bend = (sbyte)(reader.ReadByte() - 0x40) }; break; case 0xC1: command = new BendRangeCommand { Range = reader.ReadByte() }; break; case 0xC2: command = new LFOSpeedCommand { Speed = reader.ReadByte() }; break; case 0xC3: command = new LFODelayCommand { Delay = reader.ReadByte() }; break; case 0xC4: command = new ModDepthCommand { Depth = reader.ReadByte() }; break; case 0xC5: command = new ModTypeCommand { Type = reader.ReadByte() }; break; case 0xC8: command = new TuneCommand { Tune = (sbyte)(reader.ReadByte() - 0x40) }; break; case 0xCD: command = new LibraryCommand { Command = reader.ReadByte(), Argument = reader.ReadByte() }; break; case 0xCE: // EOT sbyte note; if (reader.PeekByte() < 0x80) { note = reader.ReadSByte(); prevNote = (byte)note; } else { note = -1; } command = new EndOfTieCommand { Note = note }; break; default: Console.WriteLine("Comando invalido: 0x{0:X} = {1}", off, cmd); break; } } #endregion Commands[i].Add(new SongEvent(off, command)); } } ICommand AddNoteEvent(byte note, byte velocity, byte addedDuration, byte runCmd, out byte prevNote, out byte prevVelocity) { return(new M4ANoteCommand { Note = (sbyte)(prevNote = note), Velocity = prevVelocity = velocity, Duration = (short)(runCmd == 0xCF ? -1 : (SongEvent.RestFromCMD(0xCF, runCmd) + addedDuration)) }); } }
public void InsertEvent(SongEvent e, int trackIndex, int insertIndex) { Commands[trackIndex].Insert(insertIndex, e); CalculateTicks(trackIndex); }
public void SaveAsASM(string fileName) { if (NumTracks == 0) { throw new InvalidDataException("Questa canzone non ha tracce."); } if (ROM.Instance.Game.Engine.Type != EngineType.M4A) { throw new PlatformNotSupportedException("L'esportazione in ASM da questo motore di gioco non è supportata in questo momento."); } using (var file = new StreamWriter(fileName)) { string label = Assembler.FixLabel(Path.GetFileNameWithoutExtension(fileName)); file.WriteLine("\t.include \"MPlayDef.s\""); file.WriteLine(); file.WriteLine($"\t.equ\t{label}_grp, voicegroup000"); file.WriteLine($"\t.equ\t{label}_pri, 0"); file.WriteLine($"\t.equ\t{label}_rev, 0"); file.WriteLine($"\t.equ\t{label}_mvl, 127"); file.WriteLine($"\t.equ\t{label}_key, 0"); file.WriteLine($"\t.equ\t{label}_tbs, 1"); file.WriteLine($"\t.equ\t{label}_exg, 1"); file.WriteLine($"\t.equ\t{label}_cmp, 1"); file.WriteLine(); file.WriteLine("\t.section .rodata"); file.WriteLine($"\t.global\t{label}"); file.WriteLine("\t.align\t2"); for (int i = 0; i < Commands.Length; i++) { int num = i + 1; file.WriteLine(); file.WriteLine($"@**************** Track {num} ****************@"); file.WriteLine(); file.WriteLine($"{label}_{num}:"); var offsets = Commands[i].Where(e => e.Command is CallCommand || e.Command is GoToCommand || e.Command is RepeatCommand) .Select(e => (int)(((dynamic)e.Command).Offset)).Distinct(); // Get all offsets we need labels for int jumps = 0; var labels = new Dictionary <int, string>(); foreach (int o in offsets) { labels.Add(o, $"{label}_{num}_{jumps++:D3}"); } int ticks = 0; bool displayed = false; foreach (var e in Commands[i]) { void DisplayRest(int rest) { byte amt = SongEvent.RestToCMD[rest]; file.WriteLine($"\t.byte\tW{amt:D2}"); int rem = rest - amt; if (rem != 0) { file.WriteLine($"\t.byte\tW{rem:D2}"); } ticks += rest; // TODO: Separate by 96 ticks displayed = false; } var c = e.Command; if (!displayed && ticks % 96 == 0) { file.WriteLine($"@ {ticks / 96:D3}\t----------------------------------------"); displayed = true; } int eOffset = e.GetOffset(); if (offsets.Contains(eOffset)) { file.WriteLine($"{labels[eOffset]}:"); } if (c == null) { continue; } if (c is TempoCommand tempo) { file.WriteLine($"\t.byte\tTEMPO , {tempo.Tempo}*{label}_tbs/2"); } else if (c is RestCommand rest) { DisplayRest(rest.Rest); } else if (c is NoteCommand note) { // Hide base note, velocity and duration dynamic dynote = note; byte baseDur = dynote.Duration == -1 ? (byte)0 : SongEvent.RestToCMD[dynote.Duration]; int rem = dynote.Duration - baseDur; string name = dynote.Duration == -1 ? "TIE" : $"N{baseDur:D2}"; string not = SongEvent.NoteName(dynote.Note, true); string vel = $"v{dynote.Velocity:D3}"; if (dynote.Duration != -1 && rem != 0) { file.WriteLine($"\t.byte\t\t{name} , {not} , {vel}, gtp{rem}"); } else { file.WriteLine($"\t.byte\t\t{name} , {not} , {vel}"); } } else if (c is EndOfTieCommand eot) { if (eot.Note != -1) { file.WriteLine("\t.byte\t\tEOT"); } else { file.WriteLine($"\t.byte\t\tEOT , {SongEvent.NoteName(eot.Note, true)}"); } } else if (c is VoiceCommand voice) { file.WriteLine($"\t.byte\t\tVOICE , {voice.Voice}"); } else if (c is VolumeCommand volume) { file.WriteLine($"\t.byte\t\tVOL , {volume.Volume}*{label}_mvl/mxv"); } else if (c is PanpotCommand pan) { file.WriteLine($"\t.byte\t\tPAN , {SongEvent.CenterValueString(pan.Panpot)}"); } else if (c is BendCommand bend) { file.WriteLine($"\t.byte\t\tBEND , {SongEvent.CenterValueString(bend.Bend)}"); } else if (c is TuneCommand tune) { file.WriteLine($"\t.byte\t\tTUNE , {SongEvent.CenterValueString(tune.Tune)}"); } else if (c is BendRangeCommand bendr) { file.WriteLine($"\t.byte\t\tBENDR , {bendr.Range}"); } else if (c is LFOSpeedCommand lfos) { file.WriteLine($"\t.byte\t\tLFOS , {lfos.Speed}"); } else if (c is LFODelayCommand lfodl) { file.WriteLine($"\t.byte\t\tLFODL , {lfodl.Delay}"); } else if (c is ModDepthCommand mod) { file.WriteLine($"\t.byte\t\tMOD , {mod.Depth}"); } else if (c is ModTypeCommand modt) { file.WriteLine($"\t.byte\t\tMODT , {modt.Type}"); } else if (c is PriorityCommand prio) { file.WriteLine($"\t.byte\tPRIO , {prio.Priority}"); } else if (c is KeyShiftCommand keysh) { file.WriteLine($"\t.byte\tKEYSH , {label}_key+{keysh.Shift}"); } else if (c is GoToCommand goTo) { file.WriteLine("\t.byte\tGOTO"); file.WriteLine($"\t .word\t{labels[goTo.Offset]}"); } else if (c is RepeatCommand rept) { file.WriteLine($"\t.byte\t\tREPT , {rept.Times}"); file.WriteLine($"\t .word\t{labels[rept.Offset]}"); } else if (c is M4AFinishCommand fine) { if (fine.Type == 0xB1) { file.WriteLine("\t.byte\tFINE"); } else { file.WriteLine("\t.byte\t0xB6\t@PREV"); } } else if (c is CallCommand patt) { file.WriteLine("\t.byte\tPATT"); file.WriteLine($"\t .word\t{labels[patt.Offset]}"); } else if (c is ReturnCommand pend) { file.WriteLine("\t.byte\tPEND"); } else if (c is MemoryAccessCommand memacc) { file.WriteLine($"\t.byte\t\tMEMACC, {memacc.Arg1,4}, {memacc.Arg2,4}, {memacc.Arg3}"); } else if (c is LibraryCommand xcmd) { file.WriteLine($"\t.byte\t\tXCMD , {xcmd.Command,4}, {xcmd.Argument}"); } } } file.WriteLine(); file.WriteLine("@******************************************@"); file.WriteLine("\t.align\t2"); file.WriteLine(); file.WriteLine($"{label}:"); file.WriteLine($"\t.byte\t{NumTracks}\t@ NumTrks"); file.WriteLine($"\t.byte\t{(this is M4ASong m4asong ? m4asong.Header.NumBlocks : 0)}\t@ NumBlks"); file.WriteLine($"\t.byte\t{label}_pri\t@ Priority"); file.WriteLine($"\t.byte\t{label}_rev\t@ Reverb."); file.WriteLine(); file.WriteLine($"\t.word\t{label}_grp"); file.WriteLine(); for (int i = 0; i < NumTracks; i++) { file.WriteLine($"\t.word\t{label}_{i + 1}"); } file.WriteLine(); file.WriteLine("\t.end"); } }
protected void Load(byte[] binary, M4ASongHeader head) { _binary = binary; Header = head; Array.Resize(ref Header.Tracks, Header.NumTracks); // Not really necessary yet Commands = new List <SongEvent> [Header.NumTracks]; for (int i = 0; i < Header.NumTracks; i++) { Commands[i] = new List <SongEvent>(); } VoiceTable = VoiceTable.LoadTable <M4AVoiceTable>(Header.VoiceTable); if (NumTracks == 0 || NumTracks > 16) { return; } var reader = new ROMReader(); reader.InitReader(_binary); for (int i = 0; i < NumTracks; i++) { reader.SetOffset(Header.Tracks[i]); byte cmd = 0, runCmd = 0, prevNote = 0, prevVelocity = 127; while (cmd != 0xB1 && cmd != 0xB6) { uint off = reader.Position; ICommand command = null; cmd = reader.ReadByte(); if (cmd >= 0xBD) // Commands that work within running status { runCmd = cmd; } #region TIE & Notes if (runCmd >= 0xCF && cmd < 0x80) // Within running status { var o = reader.Position; byte peek1 = reader.ReadByte(), peek2 = reader.ReadByte(); reader.SetOffset(o); if (peek1 >= 128) { command = AddNoteEvent(cmd, prevVelocity, 0, runCmd, out prevNote, out prevVelocity); } else if (peek2 > 3 || peek2 < 1) { command = AddNoteEvent(cmd, reader.ReadByte(), 0, runCmd, out prevNote, out prevVelocity); } else { command = AddNoteEvent(cmd, reader.ReadByte(), reader.ReadByte(), runCmd, out prevNote, out prevVelocity); } } else if (cmd >= 0xCF) { var o = reader.Position; byte peek1 = reader.ReadByte(), peek2 = reader.ReadByte(), peek3 = reader.ReadByte(); reader.SetOffset(o); if (peek1 >= 128) { command = AddNoteEvent(prevNote, prevVelocity, 0, runCmd, out prevNote, out prevVelocity); } else if (peek2 >= 128) { command = AddNoteEvent(reader.ReadByte(), prevVelocity, 0, runCmd, out prevNote, out prevVelocity); } // TIE cannot have an added duration so it needs to stop here else if (cmd == 0xCF || peek3 > 3 || peek3 < 1) { command = AddNoteEvent(reader.ReadByte(), reader.ReadByte(), 0, runCmd, out prevNote, out prevVelocity); } else { command = AddNoteEvent(reader.ReadByte(), reader.ReadByte(), reader.ReadByte(), runCmd, out prevNote, out prevVelocity); } } #endregion #region Rests else if (cmd >= 0x80 && cmd <= 0xB0) { command = new RestCommand { Rest = SongEvent.RestFromCMD(0x80, cmd) } } ; #endregion #region Commands else if (runCmd < 0xCF && cmd < 0x80) // Commands within running status { switch (runCmd) { case 0xBD: command = new VoiceCommand { Voice = cmd }; break; case 0xBE: command = new VolumeCommand { Volume = cmd }; break; case 0xBF: command = new PanpotCommand { Panpot = (sbyte)(cmd - 0x40) }; break; case 0xC0: command = new BendCommand { Bend = (sbyte)(cmd - 0x40) }; break; case 0xC1: command = new BendRangeCommand { Range = cmd }; break; case 0xC2: command = new LFOSpeedCommand { Speed = cmd }; break; case 0xC3: command = new LFODelayCommand { Delay = cmd }; break; case 0xC4: command = new ModDepthCommand { Depth = cmd }; break; case 0xC5: command = new ModTypeCommand { Type = cmd }; break; case 0xC8: command = new TuneCommand { Tune = (sbyte)(cmd - 0x40) }; break; case 0xCD: command = new LibraryCommand { Command = cmd, Argument = reader.ReadByte() }; break; case 0xCE: command = new EndOfTieCommand { Note = (sbyte)cmd }; prevNote = cmd; break; } } else if (cmd > 0xB0 && cmd < 0xCF) { switch (cmd) { case 0xB1: // FINE & PREV case 0xB6: command = new M4AFinishCommand { Type = cmd }; break; case 0xB2: command = new GoToCommand { Offset = reader.ReadPointer() }; break; case 0xB3: command = new CallCommand { Offset = reader.ReadPointer() }; break; case 0xB4: command = new ReturnCommand(); break; case 0xB5: command = new RepeatCommand { Times = reader.ReadByte(), Offset = reader.ReadPointer() }; break; case 0xB9: command = new MemoryAccessCommand { Arg1 = reader.ReadByte(), Arg2 = reader.ReadByte(), Arg3 = reader.ReadByte() }; break; case 0xBA: command = new PriorityCommand { Priority = reader.ReadByte() }; break; case 0xBB: command = new TempoCommand { Tempo = (ushort)(reader.ReadByte() * 2) }; break; case 0xBC: command = new KeyShiftCommand { Shift = reader.ReadSByte() }; break; // Commands that work within running status: case 0xBD: command = new VoiceCommand { Voice = reader.ReadByte() }; break; case 0xBE: command = new VolumeCommand { Volume = reader.ReadByte() }; break; case 0xBF: command = new PanpotCommand { Panpot = (sbyte)(reader.ReadByte() - 0x40) }; break; case 0xC0: command = new BendCommand { Bend = (sbyte)(reader.ReadByte() - 0x40) }; break; case 0xC1: command = new BendRangeCommand { Range = reader.ReadByte() }; break; case 0xC2: command = new LFOSpeedCommand { Speed = reader.ReadByte() }; break; case 0xC3: command = new LFODelayCommand { Delay = reader.ReadByte() }; break; case 0xC4: command = new ModDepthCommand { Depth = reader.ReadByte() }; break; case 0xC5: command = new ModTypeCommand { Type = reader.ReadByte() }; break; case 0xC8: command = new TuneCommand { Tune = (sbyte)(reader.ReadByte() - 0x40) }; break; case 0xCD: command = new LibraryCommand { Command = reader.ReadByte(), Argument = reader.ReadByte() }; break; case 0xCE: // EOT sbyte note; if (reader.PeekByte() < 128) { note = reader.ReadSByte(); prevNote = (byte)note; } else { note = -1; } command = new EndOfTieCommand { Note = note }; break; default: Console.WriteLine("Invalid command: 0x{0:X} = {1}", reader.Position, cmd); break; } } #endregion Commands[i].Add(new SongEvent(off, command)); } } ICommand AddNoteEvent(byte note, byte velocity, byte addedDuration, byte runCmd, out byte prevNote, out byte prevVelocity) { return(new M4ANoteCommand { Note = (sbyte)(prevNote = note), Velocity = prevVelocity = velocity, Duration = (short)(runCmd == 0xCF ? -1 : (SongEvent.RestFromCMD(0xCF, runCmd) + addedDuration)) }); } }