Example #1
0
        public bool UpdateFrameSkip()
        {
            if (famitrackerTempo)
            {
                return(false);
            }
            else
            {
                if (--palSkipEnvelopeCounter <= 0)
                {
                    palSkipEnvelopeIndex++;

                    if (palSkipEnvelope[palSkipEnvelopeIndex] == 0x80)
                    {
                        palSkipEnvelopeIndex = 1;
                    }

                    palSkipEnvelopeCounter = palSkipEnvelope[palSkipEnvelopeIndex];

#if DEBUG
                    var noteLength = song.GetPatternNoteLength(playPattern);
                    Debug.Assert((playNote % noteLength) != 0 || noteLength == 1);
                    //Debug.WriteLine("*** SKIP!");
#endif

                    return(palMode);
                }
                else
                {
                    return(false);
                }
            }
        }
Example #2
0
        public void AddProperties()
        {
            firstPropIdx = props.PropertyCount;

            if (song.UsesFamiTrackerTempo)
            {
                if (patternIdx < 0)
                {
                    famitrackerTempoPropIdx = props.AddNumericUpDown("Tempo :", song.FamitrackerTempo, 32, 255, TempoTooltip); // 0
                    famitrackerSpeedPropIdx = props.AddNumericUpDown("Speed :", song.FamitrackerSpeed, 1, 31, SpeedTooltip);   // 1
                }

                var notesPerBeat    = patternIdx < 0 ? song.BeatLength    : song.GetPatternBeatLength(patternIdx);
                var notesPerPattern = patternIdx < 0 ? song.PatternLength : song.GetPatternLength(patternIdx);
                var bpm             = Song.ComputeFamiTrackerBPM(song.Project.PalMode, song.FamitrackerSpeed, song.FamitrackerTempo, notesPerBeat);

                notesPerBeatPropIdx    = props.AddNumericUpDown("Notes per Beat :", notesPerBeat, 1, 256, NotesPerBeatTooltip);                        // 2
                notesPerPatternPropIdx = props.AddNumericUpDown("Notes per Pattern :", notesPerPattern, 1, Pattern.MaxLength, NotesPerPatternTooltip); // 3
                bpmLabelPropIdx        = props.AddLabel("BPM :", bpm.ToString("n1"), false, BPMTooltip);                                               // 4

                props.ShowWarnings = true;

                UpdateWarnings();
            }
            else
            {
                var noteLength      = (patternIdx < 0 ? song.NoteLength    : song.GetPatternNoteLength(patternIdx));
                var notesPerBeat    = (patternIdx < 0 ? song.BeatLength    : song.GetPatternBeatLength(patternIdx));
                var notesPerPattern = (patternIdx < 0 ? song.PatternLength : song.GetPatternLength(patternIdx));
                var groove          = (patternIdx < 0 ? song.Groove        : song.GetPatternGroove(patternIdx));

                tempoList = FamiStudioTempoUtils.GetAvailableTempos(song.Project.PalMode, notesPerBeat / noteLength);
                var tempoIndex = FamiStudioTempoUtils.FindTempoFromGroove(tempoList, groove);
                Debug.Assert(tempoIndex >= 0);
                tempoStrings = tempoList.Select(t => t.bpm.ToString("n1") + (t.groove.Length == 1 ? " *": "")).ToArray();

                var grooveList  = FamiStudioTempoUtils.GetAvailableGrooves(tempoList[tempoIndex].groove);
                var grooveIndex = Array.FindIndex(grooveList, g => Utils.CompareArrays(g, groove) == 0);
                Debug.Assert(grooveIndex >= 0);
                grooveStrings = grooveList.Select(g => string.Join("-", g)).ToArray();

                famistudioBpmPropIdx   = props.AddDropDownList("BPM : ", tempoStrings, tempoStrings[tempoIndex], BPMTooltip);                                                     // 0
                notesPerBeatPropIdx    = props.AddNumericUpDown("Notes per Beat : ", notesPerBeat / noteLength, 1, 256, NotesPerBeatTooltip);                                     // 1
                notesPerPatternPropIdx = props.AddNumericUpDown("Notes per Pattern : ", notesPerPattern / noteLength, 1, Pattern.MaxLength / noteLength, NotesPerPatternTooltip); // 2
                framesPerNotePropIdx   = props.AddLabel("Frames per Note :", noteLength.ToString(), false, FramesPerNoteTooltip);                                                 // 3

                props.ShowWarnings = true;
                props.BeginAdvancedProperties();
                groovePropIdx    = props.AddDropDownList("Groove : ", grooveStrings, grooveStrings[grooveIndex], GrooveTooltip);                                               // 4
                groovePadPropIdx = props.AddDropDownList("Groove Padding : ", GroovePaddingType.Names, GroovePaddingType.Names[song.GroovePaddingMode], GroovePaddingTooltip); // 5

                originalNoteLength   = noteLength;
                originalNotesPerBeat = notesPerBeat;

                UpdateWarnings();
            }
        }
Example #3
0
        private void ResetFamiStudioTempo(bool force)
        {
            if (!famitrackerTempo)
            {
                var newNoteLength    = song.GetPatternNoteLength(playPattern);
                var newTempoEnvelope = FamiStudioTempoUtils.GetTempoEnvelope(newNoteLength, song.Project.PalMode);

                if (newTempoEnvelope != tempoEnvelope || force)
                {
                    tempoEnvelope        = newTempoEnvelope;
                    tempoEnvelopeCounter = tempoEnvelope[0];
                    tempoEnvelopeIndex   = 0;
                }
            }
        }
Example #4
0
        protected bool PlaySongFrameInternal(bool seeking)
        {
            ClearBeat();

            //Debug.WriteLine($"PlaySongFrameInternal {playPosition}!");
            //Debug.WriteLine($"PlaySongFrameInternal {song.GetPatternStartNote(playPattern) + playNote}!");

            if (PlaybackRateShouldSkipFrame(seeking))
            {
                return(true);
            }

            // Increment before for register listeners to get correct frame number.
            frameNumber++;

            int numFramesToRun = UpdateFamiStudioTempo();

            for (int i = 0; i < numFramesToRun; i++)
            {
                if (ShouldAdvanceSong())
                {
                    //Debug.WriteLine($"  Seeking Frame {song.GetPatternStartNote(playPattern) + playNote}!");

                    if (!AdvanceSong(song.Length, seeking ? LoopMode.None : loopMode))
                    {
                        return(false);
                    }

                    AdvanceChannels();
                }

                UpdateChannels();
                UpdateTempo();

#if DEBUG
                if (i > 0)
                {
                    var noteLength = song.GetPatternNoteLength(playLocation.PatternIndex);
                    if ((playLocation.NoteIndex % noteLength) == 0 && noteLength != 1)
                    {
                        Debug.WriteLine("*********** INVALID SKIPPED NOTE!");
                    }
                }
#endif
            }

            if (!seeking)
            {
                playPosition = playLocation.ToAbsoluteNoteIndex(song);
            }

            return(true);
        }
        private int OutputSong(Song song, int songIdx, int speedChannel, int factor, bool test)
        {
            var packedPatternBuffers = new List <List <string> >(globalPacketPatternBuffers);
            var size         = 0;
            var emptyPattern = new Pattern(-1, song, 0, "");
            var emptyNote    = new Note(Note.NoteInvalid);

            for (int c = 0; c < song.Channels.Length; c++)
            {
                if (!test)
                {
                    lines.Add($"\n{ll}song{songIdx}ch{c}:");
                }

                var channel            = song.Channels[c];
                var currentSpeed       = song.FamitrackerSpeed;
                var isSpeedChannel     = c == speedChannel;
                var instrument         = (Instrument)null;
                var previousNoteLength = song.NoteLength;

                if (isSpeedChannel && project.UsesFamiTrackerTempo)
                {
                    if (!test)
                    {
                        lines.Add($"\t{db} $fb, ${song.FamitrackerSpeed:x2}");
                    }
                    size += 2;
                }

                for (int p = 0; p < song.Length; p++)
                {
                    var prevNoteValue = Note.NoteInvalid;
                    var pattern       = channel.PatternInstances[p] == null ? emptyPattern : channel.PatternInstances[p];
                    var patternBuffer = new List <string>();

                    if (p == song.LoopPoint)
                    {
                        if (!test)
                        {
                            lines.Add($"{ll}song{songIdx}ch{c}loop:");
                        }

                        // Clear stored instrument to force a reset. We might be looping
                        // to a section where the instrument was set from a previous pattern.
                        instrument = null;
                    }

                    if (isSpeedChannel && project.UsesFamiStudioTempo && machine != MachineType.NTSC)
                    {
                        var noteLength = song.GetPatternNoteLength(p);

                        if (noteLength != previousNoteLength || (p == song.LoopPoint && p != 0))
                        {
                            if (!test)
                            {
                                patternBuffer.Add($"$fb");
                                patternBuffer.Add($"{lo}({ll}tempo_env{noteLength})");
                                patternBuffer.Add($"{hi}({ll}tempo_env{noteLength})");
                                previousNoteLength = noteLength;
                            }

                            size += 3;
                        }
                    }

                    var patternLength = song.GetPatternLength(p);
                    var numValidNotes = patternLength;

                    for (var it = pattern.GetNoteIterator(0, patternLength); !it.Done;)
                    {
                        var time = it.CurrentTime;
                        var note = it.CurrentNote;

                        if (note == null)
                        {
                            note = emptyNote;
                        }

                        if (isSpeedChannel && song.UsesFamiTrackerTempo)
                        {
                            var speed = FindEffectParam(song, p, time, Note.EffectSpeed);
                            if (speed >= 0)
                            {
                                currentSpeed = speed;
                                patternBuffer.Add($"${0xfb:x2}");
                                patternBuffer.Add($"${(byte)speed:x2}");
                            }
                        }

                        it.Next();

                        if (note.HasVolume)
                        {
                            patternBuffer.Add($"${(byte)(0x70 | note.Volume):x2}");
                        }

                        if (note.HasFinePitch)
                        {
                            patternBuffer.Add($"${0x65:x2}");
                            patternBuffer.Add($"${note.FinePitch:x2}");
                        }

                        if (note.HasVibrato)
                        {
                            patternBuffer.Add($"${0x63:x2}");
                            patternBuffer.Add($"{lo}({vibratoEnvelopeNames[note.RawVibrato]})");
                            patternBuffer.Add($"{hi}({vibratoEnvelopeNames[note.RawVibrato]})");

                            if (note.RawVibrato == 0)
                            {
                                patternBuffer.Add($"${0x64:x2}");
                            }
                        }

                        if (note.HasFdsModSpeed)
                        {
                            patternBuffer.Add($"${0x66:x2}");
                            patternBuffer.Add($"${(note.FdsModSpeed >> 0) & 0xff:x2}");
                            patternBuffer.Add($"${(note.FdsModSpeed >> 8) & 0xff:x2}");
                        }

                        if (note.HasFdsModDepth)
                        {
                            patternBuffer.Add($"${0x67:x2}");
                            patternBuffer.Add($"${note.FdsModDepth:x2}");
                        }

                        if (note.IsValid)
                        {
                            // Instrument change.
                            if (note.IsMusical)
                            {
                                if (note.Instrument != instrument)
                                {
                                    int idx = instrumentIndices[note.Instrument];
                                    patternBuffer.Add($"${(byte)(0x80 | (idx << 1)):x2}");
                                    instrument = note.Instrument;
                                }
                                else if (!note.HasAttack)
                                {
                                    // TODO: Remove note entirely after a slide that matches the next note with no attack.
                                    patternBuffer.Add($"${0x62:x2}");
                                }
                            }

                            int numNotes = 0;

                            if (kernel != FamiToneKernel.FamiStudio)
                            {
                                // Note -> Empty -> Note special encoding.
                                if (time < patternLength - 2)
                                {
                                    pattern.Notes.TryGetValue(time + 1, out var nextNote1);
                                    pattern.Notes.TryGetValue(time + 2, out var nextNote2);

                                    var valid1 = (nextNote1 != null && nextNote1.IsValid) || (isSpeedChannel && FindEffectParam(song, p, time + 1, Note.EffectSpeed) >= 0);
                                    var valid2 = (nextNote2 != null && nextNote2.IsValid) || (isSpeedChannel && FindEffectParam(song, p, time + 2, Note.EffectSpeed) >= 0);

                                    if (!valid1 && valid2)
                                    {
                                        it.Next();
                                        numValidNotes--;
                                        numNotes = 1;
                                    }
                                }
                            }

                            if (note.IsSlideNote)
                            {
                                var noteTableNtsc = NesApu.GetNoteTableForChannelType(channel.Type, false, song.Project.ExpansionNumChannels);
                                var noteTablePal  = NesApu.GetNoteTableForChannelType(channel.Type, true, song.Project.ExpansionNumChannels);

                                var found = true;
                                found &= channel.ComputeSlideNoteParams(note, p, time, currentSpeed, Song.NativeTempoNTSC, noteTableNtsc, out _, out int stepSizeNtsc, out _);
                                found &= channel.ComputeSlideNoteParams(note, p, time, currentSpeed, Song.NativeTempoNTSC, noteTablePal, out _, out int stepSizePal, out _);

                                if (song.Project.UsesExpansionAudio || machine == MachineType.NTSC)
                                {
                                    stepSizePal = stepSizeNtsc;
                                }
                                else if (machine == MachineType.PAL)
                                {
                                    stepSizeNtsc = stepSizePal;
                                }

                                if (found)
                                {
                                    // Take the (signed) maximum of both notes so that we are garantee to reach our note.
                                    var stepSize = Math.Max(Math.Abs(stepSizeNtsc), Math.Abs(stepSizePal)) * Math.Sign(stepSizeNtsc);
                                    patternBuffer.Add($"${0x61:x2}");
                                    patternBuffer.Add($"${(byte)stepSize:x2}");
                                    patternBuffer.Add($"${EncodeNoteValue(c, note.Value):x2}");
                                    patternBuffer.Add($"${EncodeNoteValue(c, note.SlideNoteTarget):x2}");
                                    continue;
                                }
                            }

                            patternBuffer.Add($"${EncodeNoteValue(c, note.Value, numNotes):x2}");
                            prevNoteValue = note.Value;
                        }
                        else
                        {
                            int numEmptyNotes = 0;

                            while (!it.Done)
                            {
                                time = it.CurrentTime;
                                note = it.CurrentNote;

                                if (note == null)
                                {
                                    note = emptyNote;
                                }

                                if (numEmptyNotes >= maxRepeatCount ||
                                    note.IsValid ||
                                    note.HasVolume ||
                                    note.HasVibrato ||
                                    note.HasFinePitch ||
                                    note.HasFdsModSpeed ||
                                    note.HasFdsModDepth ||
                                    (isSpeedChannel && FindEffectParam(song, p, time, Note.EffectSpeed) >= 0))
                                {
                                    break;
                                }

                                numEmptyNotes++;
                                it.Next();
                            }

                            numValidNotes -= numEmptyNotes;
                            patternBuffer.Add($"${(byte)(0x81 | (numEmptyNotes << 1)):x2}");
                        }
                    }

                    int matchingPatternIdx = -1;

                    if (patternBuffer.Count > 0)
                    {
                        if (patternBuffer.Count > 4)
                        {
                            for (int j = 0; j < packedPatternBuffers.Count; j++)
                            {
                                if (packedPatternBuffers[j].SequenceEqual(patternBuffer))
                                {
                                    matchingPatternIdx = j;
                                    break;
                                }
                            }
                        }

                        if (matchingPatternIdx < 0)
                        {
                            if (packedPatternBuffers.Count > MaxPackedPatterns)
                            {
                                return(-1); // TODO: Error.
                            }
                            packedPatternBuffers.Add(patternBuffer);

                            size += patternBuffer.Count;

                            if (!test)
                            {
                                lines.Add($"{ll}ref{packedPatternBuffers.Count - 1}:");
                                lines.Add($"\t{db} {String.Join(",", patternBuffer)}");
                            }
                        }
                        else
                        {
                            if (!test)
                            {
                                lines.Add($"\t{db} $ff,${numValidNotes:x2}");
                                lines.Add($"\t{dw} {ll}ref{matchingPatternIdx}");
                            }

                            size += 4;
                        }
                    }
                }

                if (!test)
                {
                    lines.Add($"\t{db} $fd");
                    lines.Add($"\t{dw} {ll}song{songIdx}ch{c}loop");
                }

                size += 3;
            }

            if (!test)
            {
                globalPacketPatternBuffers = packedPatternBuffers;
            }

            return(size);
        }