private void TruncateLongPatterns(Song song) { if (song.PatternLength > 256) { song.SetDefaultPatternLength(256); } // FamiTracker can only shorten patterns using skips. // We allow patterns to be longer than the default, so we will truncate those. for (int i = 0; i < song.Length; i++) { if (song.GetPatternLength(i) > song.PatternLength) { song.ClearPatternCustomSettings(i); } } song.DeleteNotesPastMaxInstanceLength(); }
public Project Load(string filename, int songIndex, int duration, int patternLength, int startFrame, bool removeIntroSilence, bool reverseDpcm, bool preserveDpcmPad) { nsf = NsfOpen(filename); if (nsf == null) { return(null); } var trackCount = NsfGetTrackCount(nsf); if (songIndex < 0 || songIndex > trackCount) { return(null); } preserveDpcmPadding = preserveDpcmPad; var palSource = (NsfIsPal(nsf) & 1) == 1; var numFrames = duration * (palSource ? 50 : 60); project = new Project(); project.Name = Marshal.PtrToStringAnsi(NsfGetTitle(nsf)); project.Author = Marshal.PtrToStringAnsi(NsfGetArtist(nsf)); project.Copyright = Marshal.PtrToStringAnsi(NsfGetCopyright(nsf)); project.PalMode = palSource; switch (NsfGetExpansion(nsf)) { case EXTSOUND_VRC6: project.SetExpansionAudio(ExpansionType.Vrc6); break; case EXTSOUND_VRC7: project.SetExpansionAudio(ExpansionType.Vrc7); break; case EXTSOUND_FDS: project.SetExpansionAudio(ExpansionType.Fds); break; case EXTSOUND_MMC5: project.SetExpansionAudio(ExpansionType.Mmc5); break; case EXTSOUND_N163: project.SetExpansionAudio(ExpansionType.N163, GetNumNamcoChannels(filename, songIndex, numFrames)); break; case EXTSOUND_S5B: project.SetExpansionAudio(ExpansionType.S5B); break; case 0: break; default: NsfClose(nsf); // Unsupported expansion combination. return(null); } var songName = Marshal.PtrToStringAnsi(NsfGetTrackName(nsf, songIndex)); song = project.CreateSong(string.IsNullOrEmpty(songName) ? $"Song {songIndex + 1}" : songName); channelStates = new ChannelState[song.Channels.Length]; NsfSetTrack(nsf, songIndex); song.ResizeNotes(1, false); song.SetDefaultPatternLength(patternLength); for (int i = 0; i < song.Channels.Length; i++) { channelStates[i] = new ChannelState(); } var foundFirstNote = !removeIntroSilence; var p = 0; var n = 0; var f = startFrame; for (int i = 0; i < numFrames; i++) { p = f / song.PatternLength; n = f % song.PatternLength; if (p >= Song.MaxLength - 1) { break; } var playCalled = 0; do { playCalled = NsfRunFrame(nsf); }while (playCalled == 0); for (int c = 0; c < song.Channels.Length; c++) { foundFirstNote |= UpdateChannel(p, n, song.Channels[c], channelStates[c]); } if (foundFirstNote) { f++; } else { // Reset everything until we find our first note. project.DeleteAllInstrument(); project.DeleteAllSamples(); for (int c = 0; c < song.Channels.Length; c++) { channelStates[c] = new ChannelState(); } } } song.SetLength(p + 1); NsfClose(nsf); var factors = Utils.GetFactors(song.PatternLength, Song.MaxNoteLength); if (factors.Length > 0) { var noteLen = factors[0]; // Look for a factor that generates a note length < 10 and gives a pattern length that is a multiple of 16. foreach (var factor in factors) { if (factor <= 10) { noteLen = factor; if (((song.PatternLength / noteLen) % 16) == 0) { break; } } } song.ResizeNotes(noteLen, false); } else { song.ResizeNotes(1, false); } song.SetSensibleBeatLength(); song.DeleteEmptyPatterns(); song.UpdatePatternStartNotes(); project.DeleteUnusedInstruments(); project.UpdateAllLastValidNotesAndVolume(); foreach (var sample in project.Samples) { sample.ReverseBits = reverseDpcm; } return(project); }
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); }); } } }
public Project Load(string filename, int songIndex, int duration, int patternLength, int startFrame, bool removeIntroSilence) { nsf = NsfOpen(filename); if (nsf == null) { return(null); } var numFrames = duration * (NsfIsPal(nsf) != 0 ? 50 : 60); project = new Project(); project.Name = Marshal.PtrToStringAnsi(NsfGetTitle(nsf)); project.Author = Marshal.PtrToStringAnsi(NsfGetArtist(nsf)); project.Copyright = Marshal.PtrToStringAnsi(NsfGetCopyright(nsf)); switch (NsfGetExpansion(nsf)) { case EXTSOUND_VRC6: project.SetExpansionAudio(Project.ExpansionVrc6); break; case EXTSOUND_VRC7: project.SetExpansionAudio(Project.ExpansionVrc7); break; case EXTSOUND_FDS: project.SetExpansionAudio(Project.ExpansionFds); break; case EXTSOUND_MMC5: project.SetExpansionAudio(Project.ExpansionMmc5); break; case EXTSOUND_N163: project.SetExpansionAudio(Project.ExpansionN163, GetNumNamcoChannels(filename, songIndex, numFrames)); break; case EXTSOUND_S5B: project.SetExpansionAudio(Project.ExpansionS5B); break; case 0: break; default: NsfClose(nsf); // Unsupported expansion combination. return(null); } var songName = Marshal.PtrToStringAnsi(NsfGetTrackName(nsf, songIndex)); song = project.CreateSong(string.IsNullOrEmpty(songName) ? $"Song {songIndex + 1}" : songName); channelStates = new ChannelState[song.Channels.Length]; NsfSetTrack(nsf, songIndex); song.ResizeNotes(1, false); song.SetDefaultPatternLength(patternLength); for (int i = 0; i < song.Channels.Length; i++) { channelStates[i] = new ChannelState(); } var foundFirstNote = !removeIntroSilence; var p = 0; var n = 0; var f = startFrame; for (int i = 0; i < numFrames; i++) { p = f / song.PatternLength; n = f % song.PatternLength; if (p >= Song.MaxLength - 1) { break; } var playCalled = 0; do { playCalled = NsfRunFrame(nsf); }while (playCalled == 0); for (int c = 0; c < song.Channels.Length; c++) { foundFirstNote |= UpdateChannel(p, n, song.Channels[c], channelStates[c]); } if (foundFirstNote) { f++; } else { // Reset everything until we find our first note. project.DeleteAllInstrument(); project.DeleteAllSamples(); for (int c = 0; c < song.Channels.Length; c++) { channelStates[c] = new ChannelState(); } } } song.SetLength(p + 1); NsfClose(nsf); var factors = Utils.GetFactors(song.PatternLength, Song.MaxNoteLength); if (factors.Length > 0 && factors[0] <= Song.MaxNoteLength) { song.ResizeNotes(factors[0], false); } else { song.ResizeNotes(1, false); } song.SetSensibleBarLength(); song.DeleteEmptyPatterns(); song.UpdatePatternStartNotes(); project.DeleteUnusedInstruments(); project.UpdateAllLastValidNotesAndVolume(); return(project); }
public Project Load(string filename) { var envelopes = new Dictionary <int, Envelope> [Project.ExpansionCount, Envelope.Count]; var instruments = new Dictionary <int, Instrument>(); var dpcms = new Dictionary <int, DPCMSample>(); var columns = new int[5] { 1, 1, 1, 1, 1 }; for (int i = 0; i < envelopes.GetLength(0); i++) { for (int j = 0; j < envelopes.GetLength(1); j++) { envelopes[i, j] = new Dictionary <int, Envelope>(); } } project = new Project(); project.TempoMode = Project.TempoFamiTracker; DPCMSample currentDpcm = null; int dpcmWriteIdx = 0; Song song = null; string patternName = ""; var lines = File.ReadAllLines(filename); for (int i = 0; i < lines.Length; i++) { var line = lines[i].Trim(); if (line.StartsWith("TITLE")) { project.Name = line.Substring(5).Trim(' ', '"'); } else if (line.StartsWith("AUTHOR")) { project.Author = line.Substring(6).Trim(' ', '"'); } else if (line.StartsWith("COPYRIGHT")) { project.Copyright = line.Substring(9).Trim(' ', '"'); } else if (line.StartsWith("EXPANSION")) { var exp = int.Parse(line.Substring(9)); var convertedExp = ConvertExpansionAudio(exp); if (convertedExp < 0) { return(null); } project.SetExpansionAudio(convertedExp); } else if (line.StartsWith("N163CHANNELS")) { var numExpChannels = int.Parse(line.Substring(12).Trim(' ', '"')); project.SetExpansionAudio(Project.ExpansionN163, numExpChannels); } else if (line.StartsWith("MACRO")) { var expansion = line.StartsWith("MACROVRC6") ? Project.ExpansionVrc6 : line.StartsWith("MACRON163") ? Project.ExpansionN163 : Project.ExpansionNone; var halves = line.Substring(line.IndexOf(' ')).Split(':'); var param = halves[0].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); var curve = halves[1].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); var type = int.Parse(param[0]); var idx = int.Parse(param[1]); var loop = int.Parse(param[2]); var rel = int.Parse(param[3]); var famistudioType = EnvelopeTypeLookup[type]; if (famistudioType < Envelope.Count) { var env = new Envelope(famistudioType); env.Length = curve.Length; // FamiTracker allows envelope with release with no loop. We dont allow that. if (env.CanRelease && loop == -1 && rel != -1) { loop = rel; } env.Loop = loop; env.Release = env.CanRelease && rel != -1 ? rel + 1 : -1; env.Relative = famistudioType == Envelope.Pitch; for (int j = 0; j < curve.Length; j++) { env.Values[j] = sbyte.Parse(curve[j]); } envelopes[expansion, famistudioType][idx] = env; } } else if (line.StartsWith("DPCMDEF")) { var param = SplitStringKeepQuotes(line.Substring(7)); currentDpcm = CreateUniquelyNamedSample(param[2], new byte[int.Parse(param[1])]); dpcms[int.Parse(param[0])] = currentDpcm; dpcmWriteIdx = 0; } else if (line.StartsWith("DPCM")) { if (currentDpcm != null) // Can happen if more than 16KB of samples { var param = line.Substring(6).Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); foreach (var s in param) { currentDpcm.Data[dpcmWriteIdx++] = Convert.ToByte(s, 16); } } } else if (line.StartsWith("KEYDPCM")) { var param = line.Substring(7).Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (param[0] == "0") { int octave = int.Parse(param[1]); int semitone = int.Parse(param[2]); int note = octave * 12 + semitone + 1; if (project.NoteSupportsDPCM(note)) { int dpcm = int.Parse(param[3]); int pitch = int.Parse(param[4]); int loop = int.Parse(param[5]); project.MapDPCMSample(note, dpcms[dpcm], pitch, loop != 0); } } } else if (line.StartsWith("INST2A03") || line.StartsWith("INSTVRC6") || line.StartsWith("INSTN163")) { var expansion = line.StartsWith("INSTVRC6") ? Project.ExpansionVrc6 : line.StartsWith("INSTN163") ? Project.ExpansionN163 : Project.ExpansionNone; var param = SplitStringKeepQuotes(line.Substring(line.IndexOf(' '))); int idx = int.Parse(param[0]); int vol = int.Parse(param[1]); int arp = int.Parse(param[2]); int pit = int.Parse(param[3]); int dut = int.Parse(param[5]); var instrument = CreateUniquelyNamedInstrument(expansion, param[param.Length - 1]); if (expansion == Project.ExpansionN163) { instrument.N163WavePreset = Envelope.WavePresetCustom; instrument.N163WaveSize = byte.Parse(param[6]); instrument.N163WavePos = byte.Parse(param[7]); } if (vol >= 0 && instrument.IsEnvelopeActive(Envelope.Volume)) { instrument.Envelopes[Envelope.Volume] = envelopes[expansion, Envelope.Volume][vol].ShallowClone(); } if (arp >= 0 && instrument.IsEnvelopeActive(Envelope.Arpeggio)) { instrument.Envelopes[Envelope.Arpeggio] = envelopes[expansion, Envelope.Arpeggio][arp].ShallowClone(); } if (pit >= 0 && instrument.IsEnvelopeActive(Envelope.Pitch)) { instrument.Envelopes[Envelope.Pitch] = envelopes[expansion, Envelope.Pitch][pit].ShallowClone(); } if (dut >= 0 && instrument.IsEnvelopeActive(Envelope.DutyCycle)) { instrument.Envelopes[Envelope.DutyCycle] = envelopes[expansion, Envelope.DutyCycle][dut].ShallowClone(); } instruments[idx] = instrument; } else if (line.StartsWith("INSTVRC7")) { var param = SplitStringKeepQuotes(line.Substring(line.IndexOf(' '))); int idx = int.Parse(param[0]); var instrument = CreateUniquelyNamedInstrument(Project.ExpansionVrc7, param[param.Length - 1]); instrument.Vrc7Patch = byte.Parse(param[1]); if (instrument.Vrc7Patch == 0) { for (int j = 0; j < 8; j++) { instrument.Vrc7PatchRegs[j] = Convert.ToByte(param[2 + j], 16); } } instruments[idx] = instrument; } else if (line.StartsWith("INSTFDS")) { var param = SplitStringKeepQuotes(line.Substring(line.IndexOf(' '))); int idx = int.Parse(param[0]); int modEnable = int.Parse(param[1]); int modSpeed = int.Parse(param[2]); int modDepth = int.Parse(param[3]); int modDelay = int.Parse(param[4]); if (modEnable == 0) { modSpeed = 0; modDepth = 0; modDelay = 0; } instruments[idx] = CreateUniquelyNamedInstrument(Project.ExpansionFds, param[5]); instruments[idx].FdsModSpeed = (ushort)modSpeed; instruments[idx].FdsModDepth = (byte)modDepth; instruments[idx].FdsModDelay = (byte)modDelay; instruments[idx].FdsWavePreset = Envelope.WavePresetCustom; instruments[idx].FdsModPreset = Envelope.WavePresetCustom; } else if (line.StartsWith("FDSMACRO")) { var halves = line.Substring(line.IndexOf(' ')).Split(':'); var param = halves[0].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); var curve = halves[1].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); var inst = int.Parse(param[0]); var type = int.Parse(param[1]); var loop = int.Parse(param[2]); var rel = int.Parse(param[3]); var famistudioType = EnvelopeTypeLookup[type]; var env = instruments[inst].Envelopes[famistudioType]; env.Length = curve.Length; // FamiTracker allows envelope with release with no loop. We dont allow that. if (env.CanRelease && loop == -1 && rel != -1) { loop = rel; } env.Loop = loop; env.Release = env.CanRelease && rel != -1 ? rel + 1 : -1; env.Relative = famistudioType == Envelope.Pitch; for (int j = 0; j < curve.Length; j++) { env.Values[j] = sbyte.Parse(curve[j]); } } else if (line.StartsWith("FDSMOD") || line.StartsWith("FDSWAVE")) { var mod = line.StartsWith("FDSMOD"); var halves = line.Substring(line.IndexOf(' ')).Split(':'); var param = halves[0].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); var curve = halves[1].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); var inst = int.Parse(param[0]); var env = instruments[inst].Envelopes[mod ? Envelope.FdsModulation : Envelope.FdsWaveform]; for (int j = 0; j < curve.Length; j++) { env.Values[j] = sbyte.Parse(curve[j]); } if (mod) { env.ConvertFdsModulationToAbsolute(); } } else if (line.StartsWith("N163WAVE")) { var param = line.Substring(8).Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); var instIdx = int.Parse(param[0]); var waveIdx = int.Parse(param[1]); // TODO: We could create different instruments for each wave. if (waveIdx == 0) { var env = instruments[instIdx].Envelopes[Envelope.N163Waveform]; for (int j = 3; j < param.Length; j++) { env.Values[j - 3] = sbyte.Parse(param[j]); } } } else if (line.StartsWith("TRACK")) { var param = SplitStringKeepQuotes(line.Substring(5)); song = project.CreateSong(param[3]); song.SetLength(0); song.SetDefaultPatternLength(int.Parse(param[0])); song.FamitrackerSpeed = int.Parse(param[1]); song.FamitrackerTempo = int.Parse(param[2]); columns = new int[song.Channels.Length]; } else if (line.StartsWith("COLUMNS")) { var param = line.Substring(7).Split(new[] { ' ', ':' }, StringSplitOptions.RemoveEmptyEntries); for (int j = 0; j < song.Channels.Length; j++) { columns[j] = int.Parse(param[j]); } } else if (line.StartsWith("ORDER")) { var orderIdx = Convert.ToInt32(line.Substring(6, 2), 16); var values = line.Substring(5).Split(':')[1].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); var order = new int[song.Channels.Length]; for (int j = 0; j < song.Channels.Length; j++) { int patternIdx = Convert.ToInt32(values[j], 16); var name = values[j]; var pattern = song.Channels[j].GetPattern(name); if (pattern == null) { pattern = song.Channels[j].CreatePattern(name); } song.Channels[j].PatternInstances[orderIdx] = pattern; } song.SetLength(song.Length + 1); } else if (line.StartsWith("PATTERN")) { patternName = line.Substring(8); } else if (line.StartsWith("ROW")) { var channels = line.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries); var n = Convert.ToInt32(channels[0].Substring(4, 2), 16); for (int j = 1; j <= song.Channels.Length; j++) { var noteData = channels[j].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); var pattern = song.Channels[j - 1].GetPattern(patternName); if (pattern == null) { continue; } //var fxData = new RowFxData[song.PatternLength]; if (!patternFxData.ContainsKey(pattern)) { patternFxData[pattern] = new RowFxData[song.PatternLength, 4]; } // Note if (noteData[0] == "---") { pattern.GetOrCreateNoteAt(n).Value = Note.NoteStop; } else if (noteData[0] == "===") { pattern.GetOrCreateNoteAt(n).Value = Note.NoteRelease; } else if (noteData[0] != "...") { int famitoneNote; if (j == 4) { famitoneNote = (Convert.ToInt32(noteData[0].Substring(0, 1), 16) + 31) + 1; } else { int semitone = TextToNoteLookup[noteData[0].Substring(0, 2)]; int octave = noteData[0][2] - '0'; famitoneNote = octave * 12 + semitone + 1; } if (famitoneNote >= Note.MusicalNoteMin && famitoneNote <= Note.MusicalNoteMax) { var note = pattern.GetOrCreateNoteAt(n); note.Value = (byte)famitoneNote; note.Instrument = j == 5 ? null : instruments[Convert.ToInt32(noteData[1], 16)]; } else { // Note outside of range. } } // Volume if (noteData[2] != ".") { pattern.GetOrCreateNoteAt(n).Volume = Convert.ToByte(noteData[2], 16); } // Read FX. for (int k = 0; k < columns[j - 1]; k++) { var fxStr = noteData[3 + k]; if (fxStr == "...") { continue; } var fx = new RowFxData(); if (project.ExpansionAudio == Project.ExpansionFds && FdsTextToEffectLookup.TryGetValue(fxStr[0], out var fdsFx)) { fx.fx = (byte)fdsFx; } else { fx.fx = TextToEffectLookup[fxStr[0]]; } fx.param = Convert.ToByte(fxStr.Substring(1), 16); patternFxData[pattern][n, k] = fx; ApplySimpleEffects(fx, pattern, n, patternLengths); } } } } FinishImport(); return(project); }
public Project Load(string filename, int songIndex, int duration, int patternLength, int startFrame, bool removeIntroSilence, bool reverseDpcm, bool preserveDpcmPad) { nsf = NsfOpen(filename); if (nsf == IntPtr.Zero) { Log.LogMessage(LogSeverity.Error, "Error opening NSF. File may be corrupted or may be a NSF2 using advanced features such as IRQ which are not supported at the moment."); return(null); } var trackCount = NsfGetTrackCount(nsf); if (songIndex < 0 || songIndex > trackCount) { return(null); } preserveDpcmPadding = preserveDpcmPad; var palSource = (NsfIsPal(nsf) & 1) == 1; var numFrames = duration * (palSource ? 50 : 60); project = new Project(); project.Name = Marshal.PtrToStringAnsi(NsfGetTitle(nsf)); project.Author = Marshal.PtrToStringAnsi(NsfGetArtist(nsf)); project.Copyright = Marshal.PtrToStringAnsi(NsfGetCopyright(nsf)); project.PalMode = palSource; // Our expansion mask is the same as NSF. var expansionMask = NsfGetExpansion(nsf); // The 2 upper bits of the mask need to be zero, we dont support these. if (expansionMask != (expansionMask & ExpansionType.AllMask)) { Log.LogMessage(LogSeverity.Error, "NSF uses unknown or unsupported expansion chips, aborting."); NsfClose(nsf); return(null); } var numN163Channels = (expansionMask & ExpansionType.N163Mask) != 0 ? GetNumNamcoChannels(filename, songIndex, numFrames) : 1; project.SetExpansionAudioMask(expansionMask, numN163Channels); var songName = Marshal.PtrToStringAnsi(NsfGetTrackName(nsf, songIndex)); song = project.CreateSong(string.IsNullOrEmpty(songName) ? $"Song {songIndex + 1}" : songName); channelStates = new ChannelState[song.Channels.Length]; NsfSetTrack(nsf, songIndex); song.ChangeFamiStudioTempoGroove(new[] { 1 }, false); song.SetDefaultPatternLength(patternLength); for (int i = 0; i < song.Channels.Length; i++) { channelStates[i] = new ChannelState(); } var foundFirstNote = !removeIntroSilence; var p = 0; var n = 0; var f = startFrame; for (int i = 0; i < numFrames; i++) { p = f / song.PatternLength; n = f % song.PatternLength; if (p >= Song.MaxLength - 1) { break; } var playCalled = 0; var waitFrameCount = 0; do { playCalled = NsfRunFrame(nsf); if (++waitFrameCount == 1000) { Log.LogMessage(LogSeverity.Error, "NSF did not call PLAY after 1000 frames, aborting."); NsfClose(nsf); return(null); } }while (playCalled == 0); for (int c = 0; c < song.Channels.Length; c++) { foundFirstNote |= UpdateChannel(p, n, song.Channels[c], channelStates[c]); } if (foundFirstNote) { f++; } else { // Reset everything until we find our first note. project.DeleteAllInstruments(); project.DeleteAllSamples(); for (int c = 0; c < song.Channels.Length; c++) { channelStates[c] = new ChannelState(); } } } song.SetLength(p + 1); NsfClose(nsf); var factors = Utils.GetFactors(song.PatternLength, FamiStudioTempoUtils.MaxNoteLength); if (factors.Length > 0) { var noteLen = factors[0]; // Look for a factor that generates a note length < 10 and gives a pattern length that is a multiple of 16. foreach (var factor in factors) { if (factor <= 10) { noteLen = factor; if (((song.PatternLength / noteLen) % 16) == 0) { break; } } } song.ChangeFamiStudioTempoGroove(new[] { noteLen }, false); } else { song.ChangeFamiStudioTempoGroove(new[] { 1 }, false); } song.SetSensibleBeatLength(); song.ConvertToCompoundNotes(); song.DeleteEmptyPatterns(); song.UpdatePatternStartNotes(); song.InvalidateCumulativePatternCache(); project.DeleteUnusedInstruments(); foreach (var sample in project.Samples) { sample.ReverseBits = reverseDpcm; } return(project); }