BleuBleu / FamiStudio

FamiStudio NES Music Editor
MIT License
1.55k stars 95 forks source link

Feature Request: Add required flags to exported FamiStudio Music Code .s file as a comment #328

Open ChestnutDev opened 1 week ago

ChestnutDev commented 1 week ago

When I export a song to FamiStudio Music Code, the log message helpfully tells me which flags need to be set when I compile the music engine. But apart from the fact that I can't seem to copy/paste from the textbox directly (at least on Linux), it's easy to lose track of what's actually needed when re-exporting later on and different features are needed.

Screenshot from 2024-06-29 23-30-04

So I'd love for the exported .s file to contain that info, like this:

; This file is for the FamiStudio Sound Engine and was generated by FamiStudio
; Project uses release notes, you must set FAMISTUDIO_USE_RELEASE_NOTES = 1.
; Fine pitch track is used, you must set FAMISTUDIO_USE_PITCH_TRACK = 1.
; Slide notes are used, you must set FAMISTUDIO_USE_SLIDE_NOTES = 1.
; Arpeggios are used, you must set FAMISTUDIO_USE_ARPEGGIO = 1.
; DPCM Delta Counter effect is used, you must set FAMISTUDIO_USE_DELTA_COUNTER = 1.

.if FAMISTUDIO_CFG_C_BINDINGS
.export _music_data_main_menu=music_data_main_menu
.endif
; ... rest of the file ...

I've looked at the code, and it seems that a patch could be done to FamiStudio/Source/IO/FamitoneMusicFile.cs, to the Save method. I don't have a working Visual Studio right now to make a proper Pull Request, and before I do set it up again I'd like to get your input on if that's even a desirable change and if you would want it to be behind another checkbox on the Export dialog.

Here's a proposed code change to that Save method, the call to GetRequiredFlags() and the usage of usedFlags to replace the current Logging:

            var totalSongsSize = 0;
            for (int i = 0; i < project.Songs.Count; i++)
            {
                var songSize = ProcessAndOutputSong(i);
                totalSongsSize += songSize;

                if (log)
                    Log.LogMessage(LogSeverity.Info, $"Song '{project.Songs[i].Name}' size: {songSize} bytes.");
            }

            // This needs to be done at the end because the required flags are only set during the processing.
            var usedFlags = kernel == FamiToneKernel.FamiStudio ? GetRequiredFlags() : new List<string>();
            foreach (var flagMessage in usedFlags.Reverse()) {
                lines.Insert(1, $"; {flagMessage}");
            }

            File.WriteAllLines(filename, lines);

            if (includeFilename != null)
            {
                if (!project.EnsureSongAssemblyNamesAreUnique())
                {
                    return false;
                }

                OutputIncludeFile(includeFilename);
            }

            if (log)
            {
                Log.LogMessage(LogSeverity.Info, $"Total assembly file size: {headerSize + instSize + tempoSize + totalSongsSize} bytes.");

                if (project.UsesSamples)
                {
                    for (int i = 0; i < dmcSizes.Length; i++)
                    {
                        if (dmcSizes[i] != 0)
                            Log.LogMessage(LogSeverity.Info, $"DMC bank {i} file size: {dmcSizes[i]} bytes.");
                    }
                }

                if (kernel == FamiToneKernel.FamiStudio)
                {
                    foreach (var flagMessage in usedFlags) {
                        Log.LogMessage(LogSeverity.Info, flagMessage);
                    }
                }
            }

And here is that GetRequiredFlags() method:

        private IReadOnlyCollection<string> GetRequiredFlags() {
            var result = new List<string>();

             // Expansion defines
            if (project.UsesVrc6Expansion)
                result.Add("Project uses VRC6 expansion, you must set FAMISTUDIO_EXP_VRC6 = 1.");
            if (project.UsesVrc7Expansion)
                result.Add("Project uses VRCy expansion, you must set FAMISTUDIO_EXP_VRC7 = 1.");
            if (project.UsesMmc5Expansion)
                result.Add("Project uses MMC5 expansion, you must set FAMISTUDIO_EXP_MMC5 = 1.");
            if (project.UsesS5BExpansion)
                result.Add("Project uses S5B expansion, you must set FAMISTUDIO_EXP_S5B = 1.");
            if (project.UsesFdsExpansion)
                result.Add("Project uses FDS expansion, you must set FAMISTUDIO_EXP_FDS = 1.");
            if (project.UsesN163Expansion)
                result.Add($"Project uses N163 expansion, you must set FAMISTUDIO_EXP_N163 = 1 and FAMISTUDIO_EXP_N163_CHN_CNT = {project.ExpansionNumN163Channels}.");
            if (project.UsesEPSMExpansion)
                result.Add("Project uses EPSM expansion, you must set FAMISTUDIO_EXP_EPSM = 1.");

            // Feature usage defines.
            if (usesFamiTrackerTempo)
                result.Add("Project uses FamiTracker tempo, you must set FAMISTUDIO_USE_FAMITRACKER_TEMPO = 1.");
            if (usesDelayedNotesOrCuts)
                result.Add("Project uses delayed notes or cuts, you must set FAMISTUDIO_USE_FAMITRACKER_DELAYED_NOTES_OR_CUTS = 1.");
            if (usesReleaseNotes)
                result.Add("Project uses release notes, you must set FAMISTUDIO_USE_RELEASE_NOTES = 1.");
            if (usesVolumeTrack)
                result.Add("Volume track is used, you must set FAMISTUDIO_USE_VOLUME_TRACK = 1.");
            if (usesVolumeSlide)
                result.Add("Volume slides are used, you must set FAMISTUDIO_USE_VOLUME_SLIDES = 1.");
            if (usesPitchTrack)
                result.Add("Fine pitch track is used, you must set FAMISTUDIO_USE_PITCH_TRACK = 1.");
            if (usesSlideNotes)
                result.Add("Slide notes are used, you must set FAMISTUDIO_USE_SLIDE_NOTES = 1.");
            if (usesNoiseSlideNotes)
                result.Add("Slide notes are used on the noise channel, you must set FAMISTUDIO_USE_NOISE_SLIDE_NOTES = 1.");
            if (usesVibrato)
                result.Add("Vibrato effect is used, you must set FAMISTUDIO_USE_VIBRATO = 1.");
            if (usesArpeggio)
                result.Add("Arpeggios are used, you must set FAMISTUDIO_USE_ARPEGGIO = 1.");
            if (usesDutyCycleEffect)
                result.Add("Duty Cycle effect is used, you must set FAMISTUDIO_USE_DUTYCYCLE_EFFECT = 1.");
            if (usesDeltaCounter)
                result.Add("DPCM Delta Counter effect is used, you must set FAMISTUDIO_USE_DELTA_COUNTER = 1.");
            if (usesPhaseReset)
                result.Add("Phase Reset effect is used, you must set FAMISTUDIO_USE_PHASE_RESET = 1.");
            if (usesFdsAutoMod)
                result.Add("FDS auto-modulation is used on at least 1 instrument, you must set FAMISTUDIO_USE_FDS_AUTOMOD = 1.");
            if (project.SoundEngineUsesDpcmBankSwitching)
                result.Add("Project has DPCM bank-switching enabled in the project settings, you must set FAMISTUDIO_USE_DPCM_BANKSWITCHING = 1 and implement bank switching.");
            else if (project.SoundEngineUsesExtendedDpcm)
                result.Add("Project has extended DPCM mode enabled in the project settings, you must set FAMISTUDIO_USE_DPCM_EXTENDED_RANGE = 1.");
            if (project.SoundEngineUsesExtendedInstruments)
                result.Add("Project has extended instrument mode enabled in the project settings. You must set FAMISTUDIO_USE_INSTRUMENT_EXTENDED_RANGE = 1.");

            return result;
        }
BleuBleu commented 1 week ago

Thanks.

Yes I agree with that feature.

I'll try to include it in the next version. Development for the next version has started but won't come out for a while, so you may want to use your own build in the meantime.

And yes, the ability to copy the content of that log box would be useful too. Noted.

-Mat