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); } } }
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(); } }
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; } } }
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); }