Exemple #1
0
        private void Props_PropertyChanged(PropertyPage props, int propIdx, int rowIdx, int colIdx, object value)
        {
            if (song.UsesFamiTrackerTempo)
            {
                var tempo = song.FamitrackerTempo;
                var speed = song.FamitrackerSpeed;

                if (propIdx == famitrackerTempoPropIdx ||
                    propIdx == famitrackerSpeedPropIdx)
                {
                    tempo = props.GetPropertyValue <int>(famitrackerTempoPropIdx);
                    speed = props.GetPropertyValue <int>(famitrackerSpeedPropIdx);
                }

                var beatLength = props.GetPropertyValue <int>(notesPerBeatPropIdx);

                props.SetLabelText(bpmLabelPropIdx, Song.ComputeFamiTrackerBPM(song.Project.PalMode, speed, tempo, beatLength).ToString("n1"));
            }
            else
            {
                var notesPerBeat = props.GetPropertyValue <int>(notesPerBeatPropIdx);

                // Changing the number of notes in a beat will affect the list of available BPMs.
                if (propIdx == notesPerBeatPropIdx)
                {
                    tempoList    = FamiStudioTempoUtils.GetAvailableTempos(song.Project.PalMode, notesPerBeat);
                    tempoStrings = tempoList.Select(t => t.bpm.ToString("n1") + (t.groove.Length == 1 ? " *" : "")).ToArray();
                    props.UpdateDropDownListItems(famistudioBpmPropIdx, tempoStrings);
                }

                // Changing the BPM affects the grooves and note length.
                if (propIdx == famistudioBpmPropIdx ||
                    propIdx == notesPerBeatPropIdx)
                {
                    var tempoIndex    = Array.IndexOf(tempoStrings, props.GetPropertyValue <string>(famistudioBpmPropIdx));
                    var tempoInfo     = tempoList[tempoIndex];
                    var framesPerNote = Utils.Min(tempoInfo.groove);

                    props.UpdateIntegerRange(notesPerPatternPropIdx, 1, Pattern.MaxLength / framesPerNote);

                    var grooveList = FamiStudioTempoUtils.GetAvailableGrooves(tempoInfo.groove);
                    grooveStrings = grooveList.Select(g => string.Join("-", g)).ToArray();

                    props.UpdateDropDownListItems(groovePropIdx, grooveStrings);
                    props.SetLabelText(framesPerNotePropIdx, framesPerNote.ToString());
                }
            }

            UpdateWarnings();
        }
Exemple #2
0
        public static byte[] GetTempoEnvelope(int[] groove, int groovePadMode, bool palSource)
        {
            // Look in the cache first.
            var key = new CachedTempoEnvelopeKey()
            {
                groove = groove, groovePadMode = groovePadMode, palSource = palSource
            };

            if (cachedTempoEnvelopes.TryGetValue(key, out var env))
            {
                return(env);
            }

            // Otherwise build.
            var dstFactor         = palSource ? 6 : 5;
            var srcFactor         = palSource ? 5 : 6;
            var noteLength        = Utils.Min(groove);
            var grooveNumFrames   = Utils.Sum(groove);
            var grooveRepeatCount = 1;

            // Repeat the groove until we have something perfectly divisible by 6 (5 on PAL).
            while ((grooveNumFrames % srcFactor) != 0)
            {
                grooveNumFrames += Utils.Sum(groove);
                grooveRepeatCount++;
            }

            // Figure out how many frames that is on the playback machine.
            var adaptedNumFrames = grooveNumFrames / srcFactor * dstFactor;

            // Mark some frames as "important", this will typically be the first
            // and last frame of the note. This will preserve the attack and
            // 1-frame silence between notes.
            var importantFrames = new bool[grooveNumFrames];
            var frameIndex      = 0;

            for (int i = 0; i < grooveRepeatCount; i++)
            {
                for (int j = 0; j < groove.Length; j++)
                {
                    if (groove[j] == noteLength)
                    {
                        importantFrames[frameIndex] = true;
                        importantFrames[frameIndex + noteLength - 1] = true;
                    }
                    else
                    {
                        if (groovePadMode != GroovePaddingType.Beginning || noteLength == 1)
                        {
                            importantFrames[frameIndex] = true;
                        }
                        else
                        {
                            importantFrames[frameIndex + 1] = true;
                        }

                        if (groovePadMode != GroovePaddingType.End || noteLength == 1)
                        {
                            importantFrames[frameIndex + noteLength] = true;
                        }
                        else
                        {
                            importantFrames[frameIndex + noteLength - 1] = true;
                        }
                    }

                    frameIndex += groove[j];
                }
            }

#if FALSE
            var numSkipFrames = palSource ? adaptedNumFrames - grooveNumFrames : grooveNumFrames - adaptedNumFrames;
            var bestScore     = int.MaxValue;
            var bestOffset    = -1;

            for (int i = 0; i < srcFactor; i++)
            {
                var score = 0;

                frameIndex = i;
                for (int j = 0; j < numSkipFrames; j++)
                {
                    if (importantFrames[frameIndex])
                    {
                        score++;
                    }
                    frameIndex += srcFactor;
                }

                if (score < bestScore)
                {
                    bestScore  = score;
                    bestOffset = i;
                }
            }
#else
            // Start by distributing the skip (or double) frames evenly.
            var numSkipFrames = palSource ? adaptedNumFrames - grooveNumFrames : grooveNumFrames - adaptedNumFrames;
            var skipFrames    = new bool[grooveNumFrames];

            frameIndex = srcFactor / 2;
            for (int i = 0; i < numSkipFrames; i++)
            {
                skipFrames[frameIndex] = true;
                frameIndex            += srcFactor;
            }

            int GetFrameCost(int idx)
            {
                if (!skipFrames[idx])
                {
                    return(0);
                }

                var cost = 0;

                // Penalize important frames
                if (importantFrames[idx])
                {
                    cost += srcFactor;
                }

                // Look right for another skipped frame.
                for (int i = 1; i < srcFactor; i++)
                {
                    var nextIdx = idx + i;
                    if (nextIdx >= skipFrames.Length)
                    {
                        nextIdx -= skipFrames.Length;
                    }
                    if (skipFrames[nextIdx])
                    {
                        // The closer we are, the higher the cost.
                        cost += (srcFactor - i);
                        break;
                    }
                }

                // Look left for another skipped frame.
                for (int i = 1; i < srcFactor; i++)
                {
                    var prevIdx = idx - i;
                    if (prevIdx < 0)
                    {
                        prevIdx += skipFrames.Length;
                    }
                    // The closer we are, the higher the cost.
                    if (skipFrames[prevIdx])
                    {
                        cost += (srcFactor - i);
                        break;
                    }
                }

                return(cost);
            }

            var frameCosts = new int[grooveNumFrames];

            // Optimize.
            for (int i = 0; i < 100; i++)
            {
                // Update costs.
                var maxCost      = -10;
                var maxCostIndex = -1;
                var totalCost    = 0;

                for (int j = 0; j < frameCosts.Length; j++)
                {
                    var cost = GetFrameCost(j);

                    frameCosts[j] = cost;
                    totalCost    += cost;

                    if (cost > maxCost)
                    {
                        maxCost      = cost;
                        maxCostIndex = j;
                    }
                }

                if (maxCost == 0)
                {
                    break;
                }

                var currentFrameCost = GetFrameCost(maxCostIndex);

                // Try to optimize the most expensive frame by moving it to the left.
                if (maxCostIndex > 0 && !skipFrames[maxCostIndex - 1] && !importantFrames[maxCostIndex - 1])
                {
                    Utils.Swap(ref skipFrames[maxCostIndex], ref skipFrames[maxCostIndex - 1]);
                    if (GetFrameCost(maxCostIndex - 1) < currentFrameCost)
                    {
                        continue;
                    }
                    Utils.Swap(ref skipFrames[maxCostIndex], ref skipFrames[maxCostIndex - 1]);
                }

                // Try to optimize the most expensive frame by moving it to the right.
                if (maxCostIndex < skipFrames.Length - 1 && !skipFrames[maxCostIndex + 1] && !importantFrames[maxCostIndex + 1])
                {
                    Utils.Swap(ref skipFrames[maxCostIndex], ref skipFrames[maxCostIndex + 1]);
                    if (GetFrameCost(maxCostIndex + 1) < currentFrameCost)
                    {
                        continue;
                    }
                    Utils.Swap(ref skipFrames[maxCostIndex], ref skipFrames[maxCostIndex + 1]);
                }

                break;
            }
#endif

            // Build the actual envelope.
            var lastFrameIndex  = -1;
            var firstFrameIndex = -1;
            var envelope        = new List <byte>();
            var sum             = 0;

            for (int i = 0; i < skipFrames.Length; i++)
            {
                if (skipFrames[i])
                {
                    var frameDelta = i - lastFrameIndex;
                    envelope.Add((byte)(frameDelta + (palSource ? 1 : -1)));
                    sum           += frameDelta;
                    lastFrameIndex = i;
                    if (firstFrameIndex < 0)
                    {
                        firstFrameIndex = i;
                    }
                }
            }

            if (palSource)
            {
                envelope[0]--;
            }

            var remainingFrames = skipFrames.Length - sum;
            if (remainingFrames != 0)
            {
                envelope.Add((byte)(remainingFrames + firstFrameIndex + 1 + (palSource ? 1 : -1)));
            }
            envelope.Add(0x80);

            env = envelope.ToArray();
            cachedTempoEnvelopes[key] = env;

            return(env);
        }
Exemple #3
0
        public Project Load(string filename)
        {
#if !DEBUG
            try
#endif
            {
                var lines      = File.ReadAllLines(filename);
                var parameters = new Dictionary <string, string>();
                var project    = (Project)null;
                var instrument = (Instrument)null;
                var arpeggio   = (Arpeggio)null;
                var song       = (Song)null;
                var channel    = (Channel)null;
                var pattern    = (Pattern)null;

                SetInvariantCulture();

                foreach (var line in lines)
                {
                    var cmd = SplitLine(line.Trim(), ref parameters);

                    switch (cmd)
                    {
                    case "Project":
                    {
                        project = new Project();
                        parameters.TryGetValue("Version", out var version);
                        if (parameters.TryGetValue("Name", out var name))
                        {
                            project.Name = name;
                        }
                        if (parameters.TryGetValue("Author", out var author))
                        {
                            project.Author = author;
                        }
                        if (parameters.TryGetValue("Copyright", out var copyright))
                        {
                            project.Copyright = copyright;
                        }
                        if (parameters.TryGetValue("TempoMode", out var tempoMode))
                        {
                            project.TempoMode = TempoType.GetValueForName(tempoMode);
                        }
                        if (parameters.TryGetValue("PAL", out var pal))
                        {
                            project.PalMode = bool.Parse(pal);
                        }
                        if (parameters.TryGetValue("Expansions", out var expansions))
                        {
                            var expansionMask    = 0;
                            var expansionStrings = expansions.Split(',');

                            foreach (var s in expansionStrings)
                            {
                                var exp = ExpansionType.GetValueForShortName(s.Trim());
                                expansionMask |= ExpansionType.GetMaskFromValue(exp);
                            }

                            var numN163Channels = 1;
                            if ((expansionMask & ExpansionType.N163Mask) != 0 && parameters.TryGetValue("NumN163Channels", out var numN163ChannelsStr))
                            {
                                numN163Channels = int.Parse(numN163ChannelsStr);
                            }

                            project.SetExpansionAudioMask(expansionMask, numN163Channels);
                        }

                        if (!version.StartsWith("3.2"))
                        {
                            Log.LogMessage(LogSeverity.Error, "File was created with an incompatible version of FamiStudio. The text format is only compatible with the current version.");
                            return(null);
                        }
                        break;
                    }

                    case "DPCMSample":
                    {
                        var str  = parameters["Data"];
                        var data = new byte[str.Length / 2];
                        for (int i = 0; i < data.Length; i++)
                        {
                            data[i] = Convert.ToByte(str.Substring(i * 2, 2), 16);
                        }
                        var sample = project.CreateDPCMSampleFromDmcData(parameters["Name"], data);
                        break;
                    }

                    case "DPCMMapping":
                    {
                        var pitch = 15;
                        var loop  = false;
                        if (parameters.TryGetValue("Pitch", out var pitchStr))
                        {
                            pitch = int.Parse(pitchStr);
                        }
                        if (parameters.TryGetValue("Loop", out var loopStr))
                        {
                            loop = bool.Parse(loopStr);
                        }
                        project.MapDPCMSample(Note.FromFriendlyName(parameters["Note"]), project.GetSample(parameters["Sample"]), pitch, loop);
                        break;
                    }

                    case "Instrument":
                    {
                        var instrumentExp = ExpansionType.None;
                        if (parameters.TryGetValue("Expansion", out var instrumentExpStr))
                        {
                            instrumentExp = ExpansionType.GetValueForShortName(instrumentExpStr);
                        }
                        instrument = project.CreateInstrument(instrumentExp, parameters["Name"]);

                        if (instrument.IsFdsInstrument)
                        {
                            if (parameters.TryGetValue("FdsWavePreset", out var wavPresetStr))
                            {
                                instrument.FdsWavePreset = (byte)WavePresetType.GetValueForName(wavPresetStr);
                            }
                            if (parameters.TryGetValue("FdsModPreset", out var modPresetStr))
                            {
                                instrument.FdsWavePreset = (byte)WavePresetType.GetValueForName(modPresetStr);
                            }
                            if (parameters.TryGetValue("FdsMasterVolume", out var masterVolumeStr))
                            {
                                instrument.FdsMasterVolume = byte.Parse(masterVolumeStr);
                            }
                            if (parameters.TryGetValue("FdsModSpeed", out var fdsModSpeedStr))
                            {
                                instrument.FdsModSpeed = ushort.Parse(fdsModSpeedStr);
                            }
                            if (parameters.TryGetValue("FdsModDepth", out var fdsModDepthStr))
                            {
                                instrument.FdsModDepth = byte.Parse(fdsModDepthStr);
                            }
                            if (parameters.TryGetValue("FdsModDelay", out var fdsModDelayStr))
                            {
                                instrument.FdsModDelay = byte.Parse(fdsModDelayStr);
                            }
                        }
                        else if (instrument.IsN163Instrument)
                        {
                            if (parameters.TryGetValue("N163WavePreset", out var wavPresetStr))
                            {
                                instrument.N163WavePreset = (byte)WavePresetType.GetValueForName(wavPresetStr);
                            }
                            if (parameters.TryGetValue("N163WaveSize", out var n163WavSizeStr))
                            {
                                instrument.N163WaveSize = byte.Parse(n163WavSizeStr);
                            }
                            if (parameters.TryGetValue("N163WavePos", out var n163WavPosStr))
                            {
                                instrument.N163WavePos = byte.Parse(n163WavPosStr);
                            }
                        }
                        else if (instrument.IsVrc6Instrument)
                        {
                            if (parameters.TryGetValue("Vrc6SawMasterVolume", out var vrc6SawVolumeStr))
                            {
                                instrument.Vrc6SawMasterVolume = (byte)Vrc6SawMasterVolumeType.GetValueForName(vrc6SawVolumeStr);
                            }
                        }
                        else if (instrument.IsVrc7Instrument)
                        {
                            if (parameters.TryGetValue("Vrc7Patch", out var vrc7PatchStr))
                            {
                                instrument.Vrc7Patch = byte.Parse(vrc7PatchStr);
                            }

                            if (instrument.Vrc7Patch == Vrc7InstrumentPatch.Custom)
                            {
                                for (int i = 0; i < 8; i++)
                                {
                                    if (parameters.TryGetValue($"Vrc7Reg{i}", out var regStr))
                                    {
                                        instrument.Vrc7PatchRegs[i] = byte.Parse(regStr);
                                    }
                                }
                            }
                        }

                        break;
                    }

                    case "Arpeggio":
                    {
                        arpeggio = project.CreateArpeggio(parameters["Name"]);
                        arpeggio.Envelope.Length = int.Parse(parameters["Length"]);

                        if (parameters.TryGetValue("Loop", out var loopStr))
                        {
                            arpeggio.Envelope.Loop = int.Parse(loopStr);
                        }

                        var values = parameters["Values"].Split(',');
                        for (int j = 0; j < values.Length; j++)
                        {
                            arpeggio.Envelope.Values[j] = sbyte.Parse(values[j]);
                        }

                        break;
                    }

                    case "Envelope":
                    {
                        var env = instrument.Envelopes[EnvelopeType.GetValueForShortName(parameters["Type"])];
                        if (env != null)
                        {
                            if (env.CanResize)
                            {
                                env.Length = int.Parse(parameters["Length"]);
                            }

                            if (parameters.TryGetValue("Loop", out var loopStr))
                            {
                                env.Loop = int.Parse(loopStr);
                            }
                            if (parameters.TryGetValue("Release", out var releaseStr))
                            {
                                env.Release = int.Parse(releaseStr);
                            }
                            if (parameters.TryGetValue("Relative", out var relativeStr))
                            {
                                env.Relative = bool.Parse(relativeStr);
                            }

                            var values = parameters["Values"].Split(',');
                            for (int j = 0; j < values.Length; j++)
                            {
                                env.Values[j] = sbyte.Parse(values[j]);
                            }
                        }
                        break;
                    }

                    case "Song":
                    {
                        song = project.CreateSong(parameters["Name"]);
                        song.SetLength(int.Parse(parameters["Length"]));
                        song.SetBeatLength(int.Parse(parameters["BeatLength"]));
                        song.SetLoopPoint(int.Parse(parameters["LoopPoint"]));

                        if (song.UsesFamiTrackerTempo)
                        {
                            song.SetDefaultPatternLength(int.Parse(parameters["PatternLength"]));
                            song.FamitrackerTempo = int.Parse(parameters["FamiTrackerTempo"]);
                            song.FamitrackerSpeed = int.Parse(parameters["FamiTrackerSpeed"]);
                        }
                        else
                        {
                            var noteLength = int.Parse(parameters["NoteLength"]);

                            var groove            = parameters["Groove"].Split('-').Select(Int32.Parse).ToArray();
                            var groovePaddingMode = GroovePaddingType.GetValueForName(parameters["GroovePaddingMode"]);

                            if (!FamiStudioTempoUtils.ValidateGroove(groove) || Utils.Min(groove) != noteLength)
                            {
                                Log.LogMessage(LogSeverity.Error, "Invalid tempo settings.");
                                return(null);
                            }

                            song.ChangeFamiStudioTempoGroove(groove, false);
                            song.SetBeatLength(song.BeatLength * noteLength);
                            song.SetDefaultPatternLength(int.Parse(parameters["PatternLength"]) * noteLength);
                            song.SetGroovePaddingMode(groovePaddingMode);
                        }
                        break;
                    }

                    case "PatternCustomSettings":
                    {
                        if (project.UsesFamiTrackerTempo)
                        {
                            var beatLength = song.BeatLength;
                            if (parameters.TryGetValue("BeatLength", out var beatLengthStr))
                            {
                                beatLength = int.Parse(beatLengthStr);
                            }

                            song.SetPatternCustomSettings(int.Parse(parameters["Time"]), int.Parse(parameters["Length"]), beatLength);
                        }
                        else
                        {
                            var patternLength = int.Parse(parameters["Length"]);
                            var noteLength    = int.Parse(parameters["NoteLength"]);
                            var beatLength    = int.Parse(parameters["BeatLength"]);

                            var groove            = parameters["Groove"].Split('-').Select(Int32.Parse).ToArray();
                            var groovePaddingMode = GroovePaddingType.GetValueForName(parameters["GroovePaddingMode"]);

                            if (!FamiStudioTempoUtils.ValidateGroove(groove) || Utils.Min(groove) != noteLength)
                            {
                                Log.LogMessage(LogSeverity.Error, "Invalid tempo settings.");
                                return(null);
                            }

                            song.SetPatternCustomSettings(int.Parse(parameters["Time"]), patternLength * noteLength, beatLength * noteLength, groove, groovePaddingMode);
                        }
                        break;
                    }

                    case "Channel":
                    {
                        var channelType = ChannelType.GetValueForShortName(parameters["Type"]);
                        channel = song.GetChannelByType(channelType);
                        break;
                    }

                    case "Pattern":
                    {
                        pattern = channel.CreatePattern(parameters["Name"]);
                        break;
                    }

                    case "Note":
                    {
                        var time = int.Parse(parameters["Time"]);
                        var note = pattern.GetOrCreateNoteAt(time);

                        if (parameters.TryGetValue("Value", out var valueStr))
                        {
                            note.Value = (byte)Note.FromFriendlyName(valueStr);
                        }
                        if (note.IsMusical && parameters.TryGetValue("Duration", out var durationStr))
                        {
                            note.Duration = int.Parse(durationStr);
                        }
                        else if (note.IsStop)
                        {
                            note.Duration = 1;
                        }
                        if (note.IsMusical && parameters.TryGetValue("Release", out var releaseStr))
                        {
                            note.Release = int.Parse(releaseStr);
                        }
                        if (note.IsMusical && parameters.TryGetValue("Instrument", out var instStr) && channel.SupportsInstrument(project.GetInstrument(instStr)))
                        {
                            note.Instrument = project.GetInstrument(instStr);
                        }
                        if (note.IsMusical && parameters.TryGetValue("Arpeggio", out var arpStr) && channel.SupportsArpeggios)
                        {
                            note.Arpeggio = project.GetArpeggio(arpStr);
                        }
                        if (note.IsMusical && parameters.TryGetValue("SlideTarget", out var slideStr) && channel.SupportsSlideNotes)
                        {
                            note.SlideNoteTarget = (byte)Note.FromFriendlyName(slideStr);
                        }
                        if (note.IsMusical && parameters.TryGetValue("Attack", out var attackStr))
                        {
                            note.HasAttack = bool.Parse(attackStr);
                        }

                        if (parameters.TryGetValue("Volume", out var volumeStr) && channel.SupportsEffect(Note.EffectVolume))
                        {
                            note.Volume = byte.Parse(volumeStr);
                        }
                        if (parameters.TryGetValue("VolumeSlideTarget", out var volumeSlideStr) && channel.SupportsEffect(Note.EffectVolumeSlide))
                        {
                            note.VolumeSlideTarget = byte.Parse(volumeSlideStr);
                        }
                        if (parameters.TryGetValue("VibratoSpeed", out var vibSpeedStr) && channel.SupportsEffect(Note.EffectVibratoSpeed))
                        {
                            note.VibratoSpeed = byte.Parse(vibSpeedStr);
                        }
                        if (parameters.TryGetValue("VibratoDepth", out var vibDepthStr) && channel.SupportsEffect(Note.EffectVibratoDepth))
                        {
                            note.VibratoDepth = byte.Parse(vibDepthStr);
                        }
                        if (parameters.TryGetValue("Speed", out var speedStr) && channel.SupportsEffect(Note.EffectSpeed))
                        {
                            note.Speed = byte.Parse(speedStr);
                        }
                        if (parameters.TryGetValue("FinePitch", out var finePitchStr) && channel.SupportsEffect(Note.EffectFinePitch))
                        {
                            note.FinePitch = sbyte.Parse(finePitchStr);
                        }
                        if (parameters.TryGetValue("FdsModSpeed", out var modSpeedStr) && channel.SupportsEffect(Note.EffectFdsModSpeed))
                        {
                            note.FdsModSpeed = ushort.Parse(modSpeedStr);
                        }
                        if (parameters.TryGetValue("FdsModDepth", out var modDepthStr) && channel.SupportsEffect(Note.EffectFdsModDepth))
                        {
                            note.FdsModDepth = byte.Parse(modDepthStr);
                        }
                        if (parameters.TryGetValue("DutyCycle", out var dutyCycleStr) && channel.SupportsEffect(Note.EffectDutyCycle))
                        {
                            note.DutyCycle = byte.Parse(dutyCycleStr);
                        }
                        if (parameters.TryGetValue("NoteDelay", out var noteDelayStr) && channel.SupportsEffect(Note.EffectNoteDelay))
                        {
                            note.NoteDelay = byte.Parse(noteDelayStr);
                        }
                        if (parameters.TryGetValue("CutDelay", out var cutDelayStr) && channel.SupportsEffect(Note.EffectCutDelay))
                        {
                            note.CutDelay = byte.Parse(cutDelayStr);
                        }

                        break;
                    }

                    case "PatternInstance":
                    {
                        var time = int.Parse(parameters["Time"]);
                        channel.PatternInstances[time] = channel.GetPattern(parameters["Pattern"]);
                        break;
                    }
                    }
                }

                project.SortEverything(false);
                ResetCulture();

                return(project);
            }
#if !DEBUG
            catch (Exception e)
            {
                Log.LogMessage(LogSeverity.Error, "Please contact the developer on GitHub!");
                Log.LogMessage(LogSeverity.Error, e.Message);
                Log.LogMessage(LogSeverity.Error, e.StackTrace);
                ResetCulture();
                return(null);
            }
#endif
        }
Exemple #4
0
        public void ApplyAsync(bool custom, Action callback)
        {
            if (song.UsesFamiTrackerTempo)
            {
                if (patternIdx == -1)
                {
                    if (famitrackerTempoPropIdx >= 0)
                    {
                        song.FamitrackerTempo = props.GetPropertyValue <int>(famitrackerTempoPropIdx);
                        song.FamitrackerSpeed = props.GetPropertyValue <int>(famitrackerSpeedPropIdx);
                    }

                    song.SetBeatLength(props.GetPropertyValue <int>(notesPerBeatPropIdx));
                    song.SetDefaultPatternLength(props.GetPropertyValue <int>(notesPerPatternPropIdx));
                }
                else
                {
                    for (int i = minPatternIdx; i <= maxPatternIdx; i++)
                    {
                        var beatLength    = props.GetPropertyValue <int>(notesPerBeatPropIdx);
                        var patternLength = props.GetPropertyValue <int>(notesPerPatternPropIdx);

                        if (custom)
                        {
                            song.SetPatternCustomSettings(i, patternLength, beatLength);
                        }
                        else
                        {
                            song.ClearPatternCustomSettings(i);
                        }
                    }
                }

                FinishApply(callback);
            }
            else
            {
                var tempoIndex = Array.IndexOf(tempoStrings, props.GetPropertyValue <string>(famistudioBpmPropIdx));
                var tempoInfo  = tempoList[tempoIndex];

                var beatLength    = props.GetPropertyValue <int>(notesPerBeatPropIdx);
                var patternLength = props.GetPropertyValue <int>(notesPerPatternPropIdx);
                var noteLength    = Utils.Min(tempoInfo.groove);

                var grooveIndex   = Array.IndexOf(grooveStrings, props.GetPropertyValue <string>(groovePropIdx));
                var groovePadMode = GroovePaddingType.GetValueForName(props.GetPropertyValue <string>(groovePadPropIdx));
                var grooveList    = FamiStudioTempoUtils.GetAvailableGrooves(tempoInfo.groove);
                var groove        = grooveList[grooveIndex];

                props.UpdateIntegerRange(notesPerPatternPropIdx, 1, Pattern.MaxLength / noteLength);
                props.SetLabelText(framesPerNotePropIdx, noteLength.ToString());

                if (patternIdx == -1)
                {
                    ShowConvertTempoDialogAsync(noteLength != originalNoteLength, (c) =>
                    {
                        song.ChangeFamiStudioTempoGroove(groove, c);
                        song.SetBeatLength(beatLength * song.NoteLength);
                        song.SetDefaultPatternLength(patternLength * song.NoteLength);
                        song.SetGroovePaddingMode(groovePadMode);

                        FinishApply(callback);
                    });
                }
                else
                {
                    var actualNoteLength    = song.NoteLength;
                    var actualPatternLength = song.PatternLength;
                    var actualBeatLength    = song.BeatLength;

                    if (custom)
                    {
                        actualNoteLength    = noteLength;
                        actualBeatLength    = beatLength * noteLength;
                        actualPatternLength = patternLength * noteLength;
                    }

                    var patternsToResize = new List <int>();
                    for (int i = minPatternIdx; i <= maxPatternIdx; i++)
                    {
                        if (actualNoteLength != song.GetPatternNoteLength(patternIdx))
                        {
                            patternsToResize.Add(i);
                        }
                    }

                    ShowConvertTempoDialogAsync(patternsToResize.Count > 0, (c) =>
                    {
                        if (c)
                        {
                            foreach (var p in patternsToResize)
                            {
                                song.ResizePatternNotes(p, actualNoteLength);
                            }
                        }

                        for (int i = minPatternIdx; i <= maxPatternIdx; i++)
                        {
                            if (custom)
                            {
                                song.SetPatternCustomSettings(i, actualPatternLength, actualBeatLength, groove, groovePadMode);
                            }
                            else
                            {
                                song.ClearPatternCustomSettings(i);
                            }
                        }

                        FinishApply(callback);
                    });
                }
            }
        }
Exemple #5
0
        private void UpdateWarnings()
        {
            var numFramesPerPattern = 0;

            if (song.UsesFamiStudioTempo)
            {
                var tempoIndex      = Array.IndexOf(tempoStrings, props.GetPropertyValue <string>(famistudioBpmPropIdx));
                var tempoInfo       = tempoList[tempoIndex];
                var notesPerBeat    = props.GetPropertyValue <int>(notesPerBeatPropIdx);
                var notesPerPattern = props.GetPropertyValue <int>(notesPerPatternPropIdx);

                if (tempoInfo.groove.Length == 1)
                {
                    props.SetPropertyWarning(famistudioBpmPropIdx, CommentType.Good, "Ideal tempo : notes will be perfectly evenly divided.");
                }
                else if ((tempoInfo.groove.Length % notesPerBeat) == 0 ||
                         (notesPerBeat % tempoInfo.groove.Length) == 0)
                {
                    props.SetPropertyWarning(famistudioBpmPropIdx, CommentType.Warning, "Beat-aligned groove : notes will be slightly uneven, but well aligned with the beat.");
                }
                else
                {
                    props.SetPropertyWarning(famistudioBpmPropIdx, CommentType.Error, "Unaligned groove : notes will be slightly uneven and not aligned to the beat.");
                }

                if (notesPerBeat != 4)
                {
                    props.SetPropertyWarning(notesPerBeatPropIdx, CommentType.Error, "A value of 4 is strongly recommended as it gives the best range of available BPMs.");
                }
                else
                {
                    props.SetPropertyWarning(notesPerBeatPropIdx, CommentType.Good, "4 is the recommended value.");
                }

                var groovePadMode = GroovePaddingType.GetValueForName(props.GetPropertyValue <string>(groovePadPropIdx));
                numFramesPerPattern = FamiStudioTempoUtils.ComputeNumberOfFrameForGroove(notesPerPattern * Utils.Min(tempoInfo.groove), tempoInfo.groove, groovePadMode);
            }
            else if (famitrackerSpeedPropIdx >= 0)
            {
                var speed = props.GetPropertyValue <int>(famitrackerSpeedPropIdx);
                var tempo = props.GetPropertyValue <int>(famitrackerTempoPropIdx);

                if (speed == 1)
                {
                    props.SetPropertyWarning(famitrackerSpeedPropIdx, CommentType.Warning, $"A speed of 1 will not produce the same BPM between platforms (PAL/NTSC).");
                }
                else
                {
                    props.SetPropertyWarning(famitrackerSpeedPropIdx, CommentType.Good, "");
                }

                if (tempo != 150)
                {
                    props.SetPropertyWarning(famitrackerTempoPropIdx, CommentType.Warning, "A tempo of 150 is strongly recommended as it produces even notes on all platforms (NTSC/PAL).");
                }
                else
                {
                    props.SetPropertyWarning(famitrackerTempoPropIdx, CommentType.Good, "150 is the recommended value.");
                }
            }

            if (patternIdx >= 0 && numFramesPerPattern > song.PatternLength)
            {
                props.SetPropertyWarning(notesPerPatternPropIdx, CommentType.Warning, $"Pattern is longer than the song pattern length and FamiTracker does not support this. Ignore this if you are not planning to export to FamiTracker.");
            }
            else if (numFramesPerPattern >= 256)
            {
                props.SetPropertyWarning(notesPerPatternPropIdx, CommentType.Warning, $"Pattern is longer than what FamiTracker supports. Ignore this if you are not planning to export to FamiTracker.");
            }
            else
            {
                props.SetPropertyWarning(notesPerPatternPropIdx, CommentType.Good, "");
            }
        }