private void FamiTone2MusicExport(string filename, bool famiStudio) { var kernel = famiStudio ? FamiToneKernel.FamiStudio : FamiToneKernel.FamiTone2; var engineName = famiStudio ? "famistudio" : "famitone2"; var formatString = ParseOption($"{engineName}-asm-format", "nesasm"); var format = AssemblyFormat.NESASM; switch (formatString) { case "ca65": format = AssemblyFormat.CA65; break; case "asm6": format = AssemblyFormat.ASM6; break; } var extension = format == AssemblyFormat.CA65 ? ".s" : ".asm"; var seperate = HasOption($"{engineName}-asm-seperate-files"); var generateInclude = HasOption($"{engineName}-generate-list"); if (!seperate && !ValidateExtension(filename, extension)) { return; } var exportSongIds = GetExportSongIds(); if (exportSongIds != null) { if (seperate) { var songNamePattern = ParseOption($"{engineName}-asm-seperate-song-pattern", "{project}_{song}"); var dpcmNamePattern = ParseOption($"{engineName}-asm-seperate-dmc-pattern", "{project}"); foreach (var songId in exportSongIds) { var song = project.GetSong(songId); var formattedSongName = songNamePattern.Replace("{project}", project.Name).Replace("{song}", song.Name); var formattedDpcmName = dpcmNamePattern.Replace("{project}", project.Name).Replace("{song}", song.Name); var songFilename = Path.Combine(filename, Utils.MakeNiceAsmName(formattedSongName) + extension); var dpcmFilename = Path.Combine(filename, Utils.MakeNiceAsmName(formattedDpcmName) + ".dmc"); var includeFilename = generateInclude ? Path.ChangeExtension(songFilename, null) + "_songlist.inc" : null; Log.LogMessage(LogSeverity.Info, $"Exporting song '{song.Name}' as separate assembly files."); FamitoneMusicFile f = new FamitoneMusicFile(kernel, true); f.Save(project, new int[] { songId }, format, true, songFilename, dpcmFilename, includeFilename, MachineType.Dual); } } else { var includeFilename = generateInclude ? Path.ChangeExtension(filename, null) + "_songlist.inc" : null; Log.LogMessage(LogSeverity.Info, $"Exporting all songs to a single assembly file."); FamitoneMusicFile f = new FamitoneMusicFile(kernel, true); f.Save(project, exportSongIds, format, false, filename, Path.ChangeExtension(filename, ".dmc"), includeFilename, MachineType.Dual); } } }
private void ExportFamiTone2Music(bool famiStudio) { if (!canExportToSoundEngine) { return; } var props = dialog.GetPropertyPage(famiStudio ? (int)ExportFormat.FamiStudioMusic : (int)ExportFormat.FamiTone2Music); var separate = props.GetPropertyValue <bool>(1); var songIds = GetSongIds(props.GetPropertyValue <bool[]>(5)); var kernel = famiStudio ? FamiToneKernel.FamiStudio : FamiToneKernel.FamiTone2; var exportFormat = AssemblyFormat.GetValueForName(props.GetPropertyValue <string>(0)); var ext = exportFormat == AssemblyFormat.CA65 ? "s" : "asm"; var songNamePattern = props.GetPropertyValue <string>(2); var dpcmNamePattern = props.GetPropertyValue <string>(3); var generateInclude = props.GetPropertyValue <bool>(4); if (separate) { var folder = lastExportFilename != null ? lastExportFilename : PlatformUtils.ShowBrowseFolderDialog("Select the export folder", ref Settings.LastExportFolder); if (folder != null) { foreach (var songId in songIds) { var song = project.GetSong(songId); var formattedSongName = songNamePattern.Replace("{project}", project.Name).Replace("{song}", song.Name); var formattedDpcmName = dpcmNamePattern.Replace("{project}", project.Name).Replace("{song}", song.Name); var songFilename = Path.Combine(folder, Utils.MakeNiceAsmName(formattedSongName) + "." + ext); var dpcmFilename = Path.Combine(folder, Utils.MakeNiceAsmName(formattedDpcmName) + ".dmc"); var includeFilename = generateInclude ? Path.ChangeExtension(songFilename, null) + "_songlist.inc" : null; Log.LogMessage(LogSeverity.Info, $"Exporting song '{song.Name}' as separate assembly files."); FamitoneMusicFile f = new FamitoneMusicFile(kernel, true); f.Save(project, new int[] { songId }, exportFormat, true, songFilename, dpcmFilename, includeFilename, MachineType.Dual); } lastExportFilename = folder; } } else { var engineName = famiStudio ? "FamiStudio" : "FamiTone2"; var filename = lastExportFilename != null ? lastExportFilename : PlatformUtils.ShowSaveFileDialog($"Export {engineName} Assembly Code", $"{engineName} Assembly File (*.{ext})|*.{ext}", ref Settings.LastExportFolder); if (filename != null) { var includeFilename = generateInclude ? Path.ChangeExtension(filename, null) + "_songlist.inc" : null; Log.LogMessage(LogSeverity.Info, $"Exporting all songs to a single assembly file."); FamitoneMusicFile f = new FamitoneMusicFile(kernel, true); f.Save(project, songIds, exportFormat, false, filename, Path.ChangeExtension(filename, ".dmc"), includeFilename, MachineType.Dual); lastExportFilename = filename; } } }
private void FamiTone2MusicExport(string filename) { var formatString = ParseOption("famitone2-format", "nesasm"); var format = AssemblyFormat.NESASM; switch (formatString) { case "ca65": format = AssemblyFormat.CA65; break; case "asm6": format = AssemblyFormat.ASM6; break; } var extension = format == AssemblyFormat.CA65 ? ".s" : ".asm"; var seperate = HasOption("famitone2-seperate-files"); if (!seperate && !ValidateExtension(filename, extension)) { return; } var kernelString = ParseOption("famitone2-variant", "famitone2"); var kernel = FamitoneMusicFile.FamiToneKernel.FamiTone2; switch (formatString) { case "famitone2fs": kernel = FamitoneMusicFile.FamiToneKernel.FamiTone2FS; break; case "famistudio": kernel = FamitoneMusicFile.FamiToneKernel.FamiStudio; break; } var exportSongIds = GetExportSongIds(); if (exportSongIds != null) { if (seperate) { var songNamePattern = ParseOption("famitone2-seperate-song-pattern", "{project}_{song}"); var dpcmNamePattern = ParseOption("famitone2-seperate-dmc-pattern", "{project}"); foreach (var songId in exportSongIds) { var song = project.GetSong(songId); var formattedSongName = songNamePattern.Replace("{project}", project.Name).Replace("{song}", song.Name); var formattedDpcmName = dpcmNamePattern.Replace("{project}", project.Name).Replace("{song}", song.Name); var songFilename = Path.Combine(filename, Utils.MakeNiceAsmName(formattedSongName) + extension); var dpcmFilename = Path.Combine(filename, Utils.MakeNiceAsmName(formattedDpcmName) + ".dmc"); FamitoneMusicFile f = new FamitoneMusicFile(kernel); f.Save(project, new int[] { songId }, format, true, songFilename, dpcmFilename, MachineType.Dual); } } else { FamitoneMusicFile f = new FamitoneMusicFile(kernel); f.Save(project, exportSongIds, format, false, filename, Path.ChangeExtension(filename, ".dmc"), MachineType.Dual); } } }
private void OutputHeader(bool separateSongs) { string name = Utils.MakeNiceAsmName(separateSongs ? project.Songs[0].Name : project.Name); lines.Add($";this file for FamiTone2 library generated by FamiStudio"); lines.Add(""); lines.Add($"{name}_music_data:"); lines.Add($"\t{db} {project.Songs.Count}"); lines.Add($"\t{dw} {ll}instruments"); if (project.ExpansionAudio == Project.ExpansionFds || project.ExpansionAudio == Project.ExpansionN163 || project.ExpansionAudio == Project.ExpansionVrc7) { lines.Add($"\t{dw} {ll}instruments_{project.ExpansionAudioShortName.ToLower()}"); } lines.Add($"\t{dw} {ll}samples-3"); for (int i = 0; i < project.Songs.Count; i++) { var song = project.Songs[i]; var line = $"\t{dw} "; for (int chn = 0; chn < song.Channels.Length; ++chn) { line += $"{ll}song{i}ch{chn},"; } if (song.UsesFamiTrackerTempo) { int tempoPal = 256 * song.FamitrackerTempo / (50 * 60 / 24); int tempoNtsc = 256 * song.FamitrackerTempo / (60 * 60 / 24); line += $"{tempoPal},{tempoNtsc}"; lines.Add(line); } else { lines.Add(line); if (machine == MachineType.NTSC) { lines.Add($"\t{db} 0, 0, 0, 0"); } else { lines.Add($"\t{db} {lo}({ll}tempo_env{song.NoteLength}), {hi}({ll}tempo_env{song.NoteLength}), 0, 0"); } } } lines.Add(""); }
private void OutputSamples(string filename) { if (project.UsesSamples) { var sampleData = new byte[project.GetTotalSampleSize()]; foreach (var sample in project.Samples) { Array.Copy(sample.Data, 0, sampleData, project.GetAddressForSample(sample), sample.Data.Length); } var path = Path.GetDirectoryName(filename); var projectname = Utils.MakeNiceAsmName(Path.GetFileNameWithoutExtension(project.Filename)); File.WriteAllBytes(Path.Combine(path, projectname + ".dmc"), sampleData); } }
private void OutputSamples(string filename, string dmcFilename) { if (project.UsesSamples) { var sampleData = project.GetPackedSampleData(); // TODO: Once we have a real project name, we will use that. var path = Path.GetDirectoryName(filename); var projectname = Utils.MakeNiceAsmName(project.Name); if (dmcFilename == null) { dmcFilename = Path.Combine(path, projectname + ".dmc"); } File.WriteAllBytes(dmcFilename, sampleData); } }
private void OutputSamples(string filename, string dmcFilename) { if (project.UsesSamples) { var sampleData = new byte[project.GetTotalSampleSize()]; foreach (var sample in project.Samples) { Array.Copy(sample.Data, 0, sampleData, project.GetAddressForSample(sample), sample.Data.Length); } // TODO: Once we have a real project name, we will use that. var path = Path.GetDirectoryName(filename); var projectname = Utils.MakeNiceAsmName(project.Name); if (dmcFilename == null) { dmcFilename = Path.Combine(path, projectname + ".dmc"); } File.WriteAllBytes(dmcFilename, sampleData); } }
private int OutputHeader(bool separateSongs) { string name = Utils.MakeNiceAsmName(separateSongs ? project.Songs[0].Name : project.Name); lines.Add($";this file for FamiTone2 library generated by FamiStudio"); lines.Add(""); lines.Add($"{name}_music_data:"); lines.Add($"\t{db} {project.Songs.Count}"); lines.Add($"\t{dw} {ll}instruments"); lines.Add($"\t{dw} {ll}samples-3"); int size = 5; for (int i = 0; i < project.Songs.Count; i++) { var song = project.Songs[i]; var line = $"\t{dw} "; for (int chn = 0; chn < 5; ++chn) { line += $"{ll}song{i}ch{chn},"; } int tempoPal = 256 * song.Tempo / (50 * 60 / 24); int tempoNtsc = 256 * song.Tempo / (60 * 60 / 24); line += $"{tempoPal},{tempoNtsc}"; lines.Add(line); size += 14; } lines.Add(""); return(size); }
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); }
private void ExportFamiTone2() { var props = dialog.GetPropertyPage((int)ExportFormat.FamiTone2); var formatString = props.GetPropertyValue <string>(0); var ext = formatString == "CA65" ? "s" : "asm"; var separate = props.GetPropertyValue <bool>(1); var songIds = GetSongIds(props.GetPropertyValue <bool[]>(4)); var exportFormat = (FamitoneMusicFile.OutputFormat)Enum.Parse(typeof(FamitoneMusicFile.OutputFormat), formatString); var songNamePattern = props.GetPropertyValue <string>(2); var dpcmNamePattern = props.GetPropertyValue <string>(3); if (separate) { var folderBrowserDialog = new FolderBrowserDialog(); folderBrowserDialog.Description = "Select the export folder"; if (folderBrowserDialog.ShowDialog() == DialogResult.OK) { foreach (var songId in songIds) { var song = project.GetSong(songId); var formattedSongName = songNamePattern.Replace("{project}", project.Name).Replace("{song}", song.Name); var formattedDpcmName = dpcmNamePattern.Replace("{project}", project.Name).Replace("{song}", song.Name); var songFilename = Path.Combine(folderBrowserDialog.SelectedPath, Utils.MakeNiceAsmName(formattedSongName) + "." + ext); var dpcmFilename = Path.Combine(folderBrowserDialog.SelectedPath, Utils.MakeNiceAsmName(formattedDpcmName) + ".dmc"); FamitoneMusicFile f = new FamitoneMusicFile(FamitoneMusicFile.FamiToneKernel.FamiTone2); f.Save(project, new int[] { songId }, exportFormat, true, songFilename, dpcmFilename); } } } else { var filename = PlatformUtils.ShowSaveFileDialog("Export FamiTone2 Code", $"FamiTone2 Assembly File (*.{ext})|*.{ext}"); if (filename != null) { FamitoneMusicFile f = new FamitoneMusicFile(FamitoneMusicFile.FamiToneKernel.FamiTone2); f.Save(project, songIds, exportFormat, false, filename, Path.ChangeExtension(filename, ".dmc")); } } }
public bool Save(Project project, int[] songIds, int format, int machine, int kernel, string filename, string includeFilename) { SetupFormat(format); var modeStrings = new List <string>(); if (machine == MachineType.NTSC || machine == MachineType.Dual) { modeStrings.Add("ntsc"); } if (machine == MachineType.PAL || machine == 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 writes = GetRegisterWrites(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>(); var lastByteIsOperand = false; for (int i = 0; i < regs.Length; i++) { regs[i] = -1; } regs[0x00] = 0x30; regs[0x04] = 0x30; regs[0x08] = 0x80; 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); if (effect.Count == 255) { lastByteIsOperand = true; } 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 if (lastZeroVolumeIdx >= 0) { effect.RemoveRange(lastZeroVolumeIdx, effect.Count - lastZeroVolumeIdx); } if (kernel == FamiToneKernel.FamiTone2 && effect.Count > 255) { Log.LogMessage(LogSeverity.Warning, $"Effect ({song.Name}) was longer than 256 bytes ({effect.Count}) and was truncated."); var removeStart = lastByteIsOperand ? 254 : 255; effect.RemoveRange(removeStart, effect.Count - removeStart); } 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}"))}"); } Log.LogMessage(LogSeverity.Info, $"Effect ({song.Name}): {effect.Count} bytes."); } } if (format == AssemblyFormat.CA65) { lines.Add(""); lines.Add(".export sounds"); } File.WriteAllLines(filename, lines.ToArray()); if (includeFilename != null) { var includeLines = new List <string>(); for (int songIdx = 0; songIdx < songIds.Length; songIdx++) { var songId = songIds[songIdx]; var song = project.GetSong(songId); includeLines.Add($"sfx_{Utils.MakeNiceAsmName(song.Name)} = {songIdx}"); } includeLines.Add($"sfx_max = {songIds.Length}"); // For CA65, also include song names. if (format == AssemblyFormat.CA65) { includeLines.Add(""); includeLines.Add(".if SFX_STRINGS"); includeLines.Add("sfx_strings:"); foreach (var songId in songIds) { var song = project.GetSong(songId); includeLines.Add($".asciiz \"{song.Name}\""); } includeLines.Add(".endif"); } File.WriteAllLines(includeFilename, includeLines.ToArray()); } return(true); }