Example #1
0
        public bool BeginPlaySong(Song s, bool pal, int startNote, IRegisterListener listener = null)
        {
            song                   = s;
            famitrackerTempo       = song.UsesFamiTrackerTempo;
            famitrackerSpeed       = song.FamitrackerSpeed;
            famitrackerNativeTempo = pal ? Song.NativeTempoPAL : Song.NativeTempoNTSC;
            palPlayback            = pal;
            playPosition           = startNote;
            playPattern            = 0;
            playNote               = 0;
            tempoCounter           = 0;
            ResetFamiStudioTempo(true);
            channelStates = CreateChannelStates(song.Project, apuIndex, song.Project.ExpansionNumChannels, palPlayback, listener);

            NesApu.InitAndReset(apuIndex, sampleRate, palPlayback, GetNesApuExpansionAudio(song.Project), song.Project.ExpansionNumChannels, dmcCallback);

            UpdateChannelsMuting();

            //Debug.WriteLine($"START SEEKING!!");

            if (startNote != 0)
            {
                NesApu.StartSeeking(apuIndex);

                while (song.GetPatternStartNote(playPattern) + playNote < startNote)
                {
                    //Debug.WriteLine($"Seek Frame {song.GetPatternStartNote(playPattern) + playNote}!");

                    int numFramesToRun = UpdateTempoEnvelope();

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

                        AdvanceChannels();
                        UpdateChannelsEnvelopesAndAPU();

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

                NesApu.StopSeeking(apuIndex);
            }

            AdvanceChannels();
            UpdateChannelsEnvelopesAndAPU();
            EndFrame();

            playPosition = song.GetPatternStartNote(playPattern) + playNote;

            return(true);
        }
Example #2
0
        public bool BeginPlaySong(Song s, bool pal, int startNote)
        {
            song                    = s;
            famitrackerTempo        = song.UsesFamiTrackerTempo;
            famitrackerSpeed        = song.FamitrackerSpeed;
            palPlayback             = pal;
            playPosition            = startNote;
            playPattern             = 0;
            playNote                = 0;
            frameNumber             = 0;
            famitrackerTempoCounter = 0;
            channelStates           = CreateChannelStates(this, song.Project, apuIndex, song.Project.ExpansionNumChannels, palPlayback);

            NesApu.InitAndReset(apuIndex, sampleRate, palPlayback, GetNesApuExpansionAudio(song.Project.ExpansionAudio), song.Project.ExpansionNumChannels, dmcCallback);

            ResetFamiStudioTempo(true);
            UpdateChannelsMuting();

            //Debug.WriteLine($"START SEEKING!!");

            if (startNote != 0)
            {
                NesApu.StartSeeking(apuIndex);

                AdvanceChannels();
                UpdateChannels();
                UpdateFamitrackerTempo(famitrackerSpeed, song.FamitrackerTempo);

                while (song.GetPatternStartNote(playPattern) + playNote < startNote)
                {
                    if (!PlaySongFrameInternal(true))
                    {
                        break;
                    }
                }

                NesApu.StopSeeking(apuIndex);
            }
            else
            {
                AdvanceChannels();
                UpdateChannels();
                UpdateFamitrackerTempo(famitrackerSpeed, song.FamitrackerTempo);
            }

            EndFrame();

            playPosition = song.GetPatternStartNote(playPattern) + playNote;

            return(true);
        }
Example #3
0
        public bool BeginPlaySong(Song s, bool pal, int startNote)
        {
            song                   = s;
            famitrackerTempo       = song.UsesFamiTrackerTempo;
            famitrackerSpeed       = song.FamitrackerSpeed;
            famitrackerNativeTempo = pal ? Song.NativeTempoPAL : Song.NativeTempoNTSC;
            palMode                = pal;
            playPosition           = startNote;
            playPattern            = 0;
            playNote               = 0;
            tempoCounter           = 0;
            firstFrame             = true;
            ResetFamiStudioTempo(true);
            channelStates = CreateChannelStates(song.Project, apuIndex, song.Project.ExpansionNumChannels, palMode);

            NesApu.InitAndReset(apuIndex, SampleRate, palMode, GetNesApuExpansionAudio(song.Project), song.Project.ExpansionNumChannels, dmcCallback);

            if (startNote != 0)
            {
                NesApu.StartSeeking(apuIndex);
#if DEBUG
                NesApu.seeking = true;
#endif

                while (song.GetPatternStartNote(playPattern) + playNote < startNote)
                {
                    foreach (var channel in channelStates)
                    {
                        channel.Advance(song, playPattern, playNote, famitrackerSpeed, famitrackerNativeTempo);
                        channel.ProcessEffects(song, playPattern, playNote, ref famitrackerSpeed);
                        channel.UpdateEnvelopes();
                        channel.UpdateAPU();
                    }

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

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

                    UpdateFrameSkip();
                }

                NesApu.StopSeeking(apuIndex);
#if DEBUG
                NesApu.seeking = false;
#endif
            }

            return(true);
        }
Example #4
0
        public bool PlaySongFrame()
        {
            //Debug.WriteLine($"PlaySongFrame {playPosition}!");

            int numFramesToRun = UpdateTempoEnvelope();

            for (int i = 0; i < numFramesToRun; i++)
            {
                //Debug.WriteLine($"  Running Frame {playPosition}!");

                if (UpdateTempo(famitrackerSpeed, song.FamitrackerTempo))
                {
                    // Advance to next note.
                    if (!AdvanceSong(song.Length, loopMode))
                    {
                        return(false);
                    }

                    AdvanceChannels();

                    playPosition = song.GetPatternStartNote(playPattern) + playNote;
                }

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

                // Update envelopes + APU registers.
                foreach (var channel in channelStates)
                {
                    channel.UpdateEnvelopes();
                    channel.UpdateAPU();
                }
            }

            UpdateChannelsMuting();
            EndFrame();

            return(true);
        }
Example #5
0
        protected bool PlaySongFrameInternal(bool seeking)
        {
            //Debug.WriteLine($"PlaySongFrameInternal {playPosition}!");
            //Debug.WriteLine($"PlaySongFrameInternal {song.GetPatternStartNote(playPattern) + playNote}!");

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

            int numFramesToRun = UpdateFamiStudioTempo();

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

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

                    AdvanceChannels();
                }

                UpdateChannels();
                UpdateFamitrackerTempo(famitrackerSpeed, song.FamitrackerTempo);

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

            if (!seeking)
            {
                playPosition = song.GetPatternStartNote(playPattern) + playNote;
            }

            return(true);
        }
Example #6
0
        public bool PlaySongFrame()
        {
            do
            {
                if (firstFrame || UpdateTempo(famitrackerSpeed, song.FamitrackerTempo))
                {
                    // Advance to next note.
                    if (!firstFrame && !AdvanceSong(song.Length, loopMode))
                    {
                        return(false);
                    }

                    foreach (var channel in channelStates)
                    {
                        channel.Advance(song, playPattern, playNote, famitrackerSpeed, famitrackerNativeTempo);
                        channel.ProcessEffects(song, playPattern, playNote, ref famitrackerSpeed);
                    }

                    playPosition = song.GetPatternStartNote(playPattern) + playNote;
                }

                // Debug.WriteLine($"Running Frame {playPosition}!");

                // Update envelopes + APU registers.
                foreach (var channel in channelStates)
                {
                    channel.UpdateEnvelopes();
                    channel.UpdateAPU();
                }
            }while (!firstFrame && UpdateFrameSkip());

            firstFrame = false;

            // Mute.
            for (int i = 0; i < channelStates.Length; i++)
            {
                NesApu.EnableChannel(apuIndex, i, (channelMask & (1 << i)));
            }

            EndFrame();

            return(true);
        }
Example #7
0
        public int FindNextNoteForSlide(int patternIdx, int noteIdx, int maxNotes)
        {
            var noteCount     = 0;
            var patternLength = song.GetPatternLength(patternIdx);
            var pattern       = patternInstances[patternIdx];

            for (var it = pattern.GetNoteIterator(noteIdx + 1, patternLength); !it.Done && noteCount < maxNotes; it.Next(), noteCount++)
            {
                var time = it.CurrentTime;
                var note = it.CurrentNote;
                if (note != null && (note.IsMusical || note.IsStop))
                {
                    return(song.GetPatternStartNote(patternIdx, time) - song.GetPatternStartNote(patternIdx, noteIdx));
                }
            }

            for (int p = patternIdx + 1; p < song.Length && noteCount < maxNotes; p++)
            {
                pattern = patternInstances[p];
                if (pattern != null && pattern.FirstValidNoteTime >= 0)
                {
                    return(song.GetPatternStartNote(p, pattern.FirstValidNoteTime) - song.GetPatternStartNote(patternIdx, noteIdx));
                }
                else
                {
                    noteCount += song.GetPatternLength(p);
                }
            }

            // This mean we hit the end of the song.
            if (noteCount < maxNotes)
            {
                return(song.GetPatternStartNote(song.Length) - song.GetPatternStartNote(patternIdx, noteIdx));
            }

            return(maxNotes);
        }
Example #8
0
        private void CreateSlideNotes(Song s, Dictionary <Pattern, RowFxData[, ]> patternFxData)
        {
            var processedPatterns = new HashSet <Pattern>();

            // Convert slide notes + portamento to our format.
            foreach (var c in s.Channels)
            {
                if (!c.SupportsSlideNotes)
                {
                    continue;
                }

                var songSpeed          = s.FamitrackerSpeed;
                var lastNoteInstrument = (Instrument)null;
                var lastNoteValue      = (byte)Note.NoteInvalid;
                var portamentoSpeed    = 0;
                var slideSpeed         = 0;
                var slideShift         = c.IsN163WaveChannel ? 2 : 0;
                var slideSign          = c.IsN163WaveChannel || c.IsFdsWaveChannel ? -1 : 1; // Inverted channels.

                for (int p = 0; p < s.Length; p++)
                {
                    var pattern = c.PatternInstances[p];

                    if (pattern == null || !patternFxData.ContainsKey(pattern) || processedPatterns.Contains(pattern))
                    {
                        continue;
                    }

                    processedPatterns.Add(pattern);

                    var fxData     = patternFxData[pattern];
                    var patternLen = s.GetPatternLength(p);

                    for (var it = pattern.GetNoteIterator(0, patternLen); !it.Done; it.Next())
                    {
                        var n    = it.CurrentTime;
                        var note = it.CurrentNote;

                        // Look for speed changes.
                        foreach (var c2 in s.Channels)
                        {
                            var pattern2 = c2.PatternInstances[p];

                            if (pattern2 != null && pattern2.Notes.TryGetValue(n, out var note2) && note2.HasSpeed)
                            {
                                songSpeed = note2.Speed;
                            }
                        }

                        var slideTarget = 0;

                        for (int i = 0; i < fxData.GetLength(1); i++)
                        {
                            var fx = fxData[n, i];

                            if (fx.param != 0)
                            {
                                // When the effect it turned on, we need to add a note.
                                if ((fx.fx == Effect_PortaUp ||
                                     fx.fx == Effect_PortaDown ||
                                     fx.fx == Effect_SlideUp ||
                                     fx.fx == Effect_SlideDown) &&
                                    lastNoteValue >= Note.MusicalNoteMin &&
                                    lastNoteValue <= Note.MusicalNoteMax && (note == null || !note.IsValid))
                                {
                                    if (note == null)
                                    {
                                        note = pattern.GetOrCreateNoteAt(n);
                                        it.Resync();
                                    }

                                    note.Value      = lastNoteValue;
                                    note.Instrument = lastNoteInstrument;
                                    note.HasAttack  = false;
                                }
                            }

                            if (fx.fx == Effect_PortaUp)
                            {
                                // If we have a Qxx/Rxx on the same row as a 1xx/2xx, things get weird.
                                if (slideTarget == 0)
                                {
                                    slideSpeed = (-fx.param * slideSign) << slideShift;
                                }
                            }
                            if (fx.fx == Effect_PortaDown)
                            {
                                // If we have a Qxx/Rxx on the same row as a 1xx/2xx, things get weird.
                                if (slideTarget == 0)
                                {
                                    slideSpeed = (fx.param * slideSign) << slideShift;
                                }
                            }
                            if (fx.fx == Effect_Portamento)
                            {
                                portamentoSpeed = fx.param;
                            }
                            if (fx.fx == Effect_SlideUp)
                            {
                                slideTarget = note.Value + (fx.param & 0xf);
                                slideSpeed  = (-((fx.param >> 4) * 2 + 1)) << slideShift;
                            }
                            if (fx.fx == Effect_SlideDown)
                            {
                                slideTarget = note.Value - (fx.param & 0xf);
                                slideSpeed  = (((fx.param >> 4) * 2 + 1)) << slideShift;
                            }
                        }

                        // Create a slide note.
                        if (note != null && !note.IsSlideNote)
                        {
                            if (note.IsMusical)
                            {
                                var slideSource = note.Value;
                                var noteTable   = NesApu.GetNoteTableForChannelType(c.Type, false, s.Project.ExpansionNumChannels);
                                var pitchLimit  = NesApu.GetPitchLimitForChannelType(c.Type);

                                // If we have a new note with auto-portamento enabled, we need to
                                // swap the notes since our slide notes work backward compared to
                                // FamiTracker.
                                if (portamentoSpeed != 0)
                                {
                                    // Ignore notes with no attack since we created them to handle a previous slide.
                                    if (note.HasAttack && lastNoteValue >= Note.MusicalNoteMin && lastNoteValue <= Note.MusicalNoteMax)
                                    {
                                        slideSpeed  = portamentoSpeed;
                                        slideTarget = note.Value;
                                        slideSource = lastNoteValue;
                                        note.Value  = lastNoteValue;
                                    }
                                }

                                // Our implementation of VRC7 pitches is quite different from FamiTracker.
                                // Compensate for larger pitches in higher octaves by shifting. We cant shift by
                                // a large amount because the period is 9-bit and FamiTracker is restricted to
                                // this for slides (octave never changes).
                                var octaveSlideShift = c.IsVrc7FmChannel && note.Value >= 12 ? 1 : 0;

                                if (slideTarget != 0)
                                {
                                    // TODO: We assume a tempo of 150 here. This is wrong.
                                    var numFrames = Math.Max(1, Math.Abs((noteTable[slideSource] - noteTable[slideTarget]) / ((slideSpeed << octaveSlideShift) * songSpeed)));
                                    note.SlideNoteTarget = (byte)slideTarget;

                                    var nn = n + numFrames;
                                    var np = p;
                                    while (nn >= s.GetPatternLength(np))
                                    {
                                        nn -= s.GetPatternLength(np);
                                        np++;
                                    }
                                    if (np >= s.Length)
                                    {
                                        np = s.Length;
                                        nn = 0;
                                    }

                                    // Still to see if there is a note between the current one and the
                                    // next note, this could append if you add a note before the slide
                                    // is supposed to finish.
                                    if (FindNextNoteForSlide(c, p, n, out var np2, out var nn2, patternFxData))
                                    {
                                        if (np2 < np)
                                        {
                                            np = np2;
                                            nn = nn2;
                                        }
                                        else if (np2 == np)
                                        {
                                            nn = Math.Min(nn, nn2);
                                        }
                                    }

                                    if (np < s.Length)
                                    {
                                        // Add an extra note with no attack to stop the slide.
                                        var nextPattern = c.PatternInstances[np];
                                        if (!nextPattern.Notes.TryGetValue(nn, out var nextNote) || !nextNote.IsValid)
                                        {
                                            nextNote            = nextPattern.GetOrCreateNoteAt(nn);
                                            nextNote.Instrument = note.Instrument;
                                            nextNote.Value      = (byte)slideTarget;
                                            nextNote.HasAttack  = false;
                                            it.Resync();
                                        }
                                    }

                                    // 3xx, Qxx and Rxx stops when its done.
                                    slideSpeed = 0;
                                }
                                // Find the next note that would stop the slide or change the FX settings.
                                else if (slideSpeed != 0 && FindNextNoteForSlide(c, p, n, out var np, out var nn, patternFxData))
                                {
                                    // Compute the pitch delta and find the closest target note.
                                    var numFrames = (s.GetPatternStartNote(np, nn) - s.GetPatternStartNote(p, n)) * songSpeed;

                                    // TODO: PAL.
                                    var newNotePitch = Utils.Clamp(noteTable[slideSource] + numFrames * (slideSpeed << octaveSlideShift), 0, pitchLimit);
                                    var newNote      = FindBestMatchingNote(noteTable, newNotePitch, Math.Sign(slideSpeed));

                                    note.SlideNoteTarget = (byte)newNote;

                                    // If the FX was turned off, we need to add an extra note.
                                    var nextPattern = c.PatternInstances[np];
                                    if (!nextPattern.Notes.TryGetValue(nn, out var nextNote) || !nextNote.IsValid)
                                    {
                                        nextNote            = nextPattern.GetOrCreateNoteAt(nn);
                                        nextNote.Instrument = note.Instrument;
                                        nextNote.Value      = (byte)newNote;
                                        nextNote.HasAttack  = false;
                                        it.Resync();
                                    }
                                }
                            }
                        }

                        if (note != null && (note.IsMusical || note.IsStop))
                        {
                            lastNoteValue      = note.IsSlideNote ? note.SlideNoteTarget : note.Value;
                            lastNoteInstrument = note.Instrument;
                        }
                    }
                }
            }
        }