public bool Save(Project project, int[] songIds, AssemblyFormat format, MachineType mode, string filename) { SetupFormat(format); var modeStrings = new List <string>(); if (mode == MachineType.NTSC || mode == MachineType.Dual) { modeStrings.Add("ntsc"); } if (mode == MachineType.PAL || mode == MachineType.Dual) { modeStrings.Add("pal"); } var lines = new List <string>(); lines.Add($";this file for FamiTone2 libary generated by FamiStudio\n"); lines.Add($"sounds:"); lines.Add($"\t{dw} {ll}{modeStrings[0]}"); lines.Add($"\t{dw} {ll}{modeStrings[1 % modeStrings.Count]}"); foreach (var str in modeStrings) { lines.Add($"{ll}{str}:"); foreach (var songId in songIds) { var song = project.GetSong(songId); lines.Add($"\t{dw} {ll}sfx_{str}_{Utils.MakeNiceAsmName(song.Name)}"); } lines.Add(""); } foreach (var str in modeStrings) { foreach (var songId in songIds) { var song = project.GetSong(songId); var regPlayer = new RegisterPlayer(); var writes = regPlayer.GetRegisterValues(song, str == "pal"); var lastChangeFrame = 0; var lastZeroVolumeIdx = -1; var volumeAllZero = true; var volume = new int[4]; var regs = new int[32]; var effect = new List <byte>(); for (int i = 0; i < regs.Length; i++) { regs[i] = -1; } regs[0x00] = 0x30; regs[0x04] = 0x30; regs[0x08] = 0x00; regs[0x0c] = 0x30; foreach (var reg in writes) { if (reg.Register == NesApu.APU_PL1_VOL || reg.Register == NesApu.APU_PL1_LO || reg.Register == NesApu.APU_PL1_HI || reg.Register == NesApu.APU_PL2_VOL || reg.Register == NesApu.APU_PL2_LO || reg.Register == NesApu.APU_PL2_HI || reg.Register == NesApu.APU_TRI_LINEAR || reg.Register == NesApu.APU_TRI_LO || reg.Register == NesApu.APU_TRI_HI || reg.Register == NesApu.APU_NOISE_VOL || reg.Register == NesApu.APU_NOISE_LO) { if (regs[reg.Register - 0x4000] != reg.Value) { if (reg.FrameNumber != lastChangeFrame) { int numEmptyFrames = reg.FrameNumber - lastChangeFrame; while (numEmptyFrames >= 0) { effect.Add((byte)(Math.Min(numEmptyFrames, 127))); numEmptyFrames -= 127; } } switch (reg.Register) { case 0x4000: volume[0] = reg.Value & 0x0f; break; case 0x4004: volume[1] = reg.Value & 0x0f; break; case 0x4008: volume[2] = reg.Value & 0x7f; break; case 0x400c: volume[3] = reg.Value & 0x0f; break; } if (!volumeAllZero) { if (volume[0] == 0 && volume[1] == 0 && volume[2] == 0 && volume[3] == 0) { volumeAllZero = true; lastZeroVolumeIdx = effect.Count(); } } else { if (volume[0] != 0 || volume[1] != 0 || volume[2] != 0 || volume[3] != 0) { volumeAllZero = false; } } effect.Add(RegisterMap[reg.Register - 0x4000]); effect.Add((byte)reg.Value); regs[reg.Register - 0x4000] = reg.Value; lastChangeFrame = reg.FrameNumber; } } } if (!volumeAllZero) { int numEmptyFrames = writes[writes.Length - 1].FrameNumber - lastChangeFrame; while (numEmptyFrames > 0) { effect.Add((byte)(Math.Min(numEmptyFrames, 127))); numEmptyFrames -= 127; } } else { effect.RemoveRange(lastZeroVolumeIdx, effect.Count - lastZeroVolumeIdx); } // TODO: Error management! if (effect.Count > 255) { effect.RemoveRange(lastZeroVolumeIdx, effect.Count - 255); } effect.Add(0); lines.Add($"{ll}sfx_{str}_{Utils.MakeNiceAsmName(song.Name)}:"); for (int i = 0; i < (effect.Count + 15) / 16; i++) { lines.Add($"\t{db} {string.Join(",", effect.Skip(i * 16).Take(Math.Min(16, effect.Count - i * 16)).Select(x => $"${x:x2}"))}"); } } } File.WriteAllLines(filename, lines.ToArray()); return(true); }