protected DPCMSample CreateUniquelyNamedSample(string baseName, byte[] data) { string name = baseName; var j = 2; while (!project.IsDPCMSampleNameUnique(name)) { name = baseName + "-" + j++; } return(project.CreateDPCMSample(name, data)); }
public static Instrument CreateFromFile(Project project, string filename) { var bytes = System.IO.File.ReadAllBytes(filename); if (!bytes.Skip(0).Take(3).SequenceEqual(Encoding.ASCII.GetBytes("FTI"))) { return(null); } if (!bytes.Skip(3).Take(3).SequenceEqual(Encoding.ASCII.GetBytes("2.4"))) { return(null); } var instType = (InstrumentType)bytes[6]; // Needs to match the current expansion audio. Our enum happens to match (-1) for now. if (instType != InstrumentType.INST_2A03 && (int)(instType - 1) != project.ExpansionAudio) { return(null); } var offset = 7; var nameLen = BitConverter.ToInt32(bytes, offset); offset += 4; var name = Encoding.ASCII.GetString(bytes, offset, nameLen); offset += nameLen; if (bytes[offset++] != (int)SequenceType.SEQ_COUNT) { return(null); } if (project.GetInstrument(name) != null) { return(null); } var instrument = project.CreateInstrument((int)instType - 1, name); // Envelopes for (int i = 0; i < (int)SequenceType.SEQ_COUNT; i++) { if (bytes[offset++] == 1) { var itemCount = BitConverter.ToInt32(bytes, offset); offset += 4; var loopPoint = BitConverter.ToInt32(bytes, offset); offset += 4; var releasePoint = BitConverter.ToInt32(bytes, offset); offset += 4; var setting = BitConverter.ToInt32(bytes, offset); offset += 4; var seq = new sbyte[itemCount]; for (int j = 0; j < itemCount; j++) { seq[j] = (sbyte)bytes[offset++]; } if (releasePoint >= 0 && i != (int)SequenceType.SEQ_VOLUME) { releasePoint = -1; } // FamiTracker allows envelope with release with no loop. We dont allow that. if (i == (int)SequenceType.SEQ_VOLUME && releasePoint != -1) { if (loopPoint == -1) { loopPoint = releasePoint; } if (releasePoint != -1) { releasePoint++; } } Envelope env = null; switch ((SequenceType)i) { case SequenceType.SEQ_VOLUME: env = instrument.Envelopes[Envelope.Volume]; break; case SequenceType.SEQ_PITCH: env = instrument.Envelopes[Envelope.Pitch]; env.Relative = true; break; case SequenceType.SEQ_ARPEGGIO: env = instrument.Envelopes[Envelope.Arpeggio]; break; case SequenceType.SEQ_DUTYCYCLE: instrument.DutyCycle = seq[0]; break; } if (env != null) { env.Length = itemCount; env.Loop = loopPoint; env.Release = releasePoint; Array.Copy(seq, 0, env.Values, 0, itemCount); } } } // Samples if (instType == InstrumentType.INST_2A03) { // Skip over the sample mappings for now, we will load them after the actual sample data. var assignedCount = BitConverter.ToInt32(bytes, offset); offset += 4; var mappingOffset = offset; offset += assignedCount * 4; var sampleCount = BitConverter.ToInt32(bytes, offset); offset += 4; var sampleMap = new DPCMSample[sampleCount]; for (int i = 0; i < sampleCount; i++) { var sampleIdx = BitConverter.ToInt32(bytes, offset); offset += 4; var sampleNameLen = BitConverter.ToInt32(bytes, offset); offset += 4; var sampleName = Encoding.ASCII.GetString(bytes, offset, sampleNameLen); offset += sampleNameLen; var sampleSize = BitConverter.ToInt32(bytes, offset); offset += 4; var sampleData = new byte[sampleSize]; Array.Copy(bytes, offset, sampleData, 0, sampleSize); offset += sampleSize; sampleMap[sampleIdx] = project.CreateDPCMSample(sampleName, sampleData); } for (int i = 0; i < assignedCount; i++) { byte idx = bytes[mappingOffset++]; byte sample = bytes[mappingOffset++]; byte pitch = bytes[mappingOffset++]; byte delta = bytes[mappingOffset++]; if (project.NoteSupportsDPCM(idx + 1)) { project.MapDPCMSample(idx + 1, sampleMap[sample - 1], pitch); } } } return(instrument); }
public static Project Load(string filename) { var project = new Project(); var envelopes = new Dictionary <int, Envelope>[Envelope.Max] { new Dictionary <int, Envelope>(), new Dictionary <int, Envelope>(), new Dictionary <int, Envelope>() }; var duties = new Dictionary <int, int>(); var instruments = new Dictionary <int, Instrument>(); var dpcms = new Dictionary <int, DPCMSample>(); var columns = new int[5] { 1, 1, 1, 1, 1 }; var noteLookup = new Dictionary <string, int> { ["A-"] = 9, ["A#"] = 10, ["B-"] = 11, ["C-"] = 0, ["C#"] = 1, ["D-"] = 2, ["D#"] = 3, ["E-"] = 4, ["F-"] = 5, ["F#"] = 6, ["G-"] = 7, ["G#"] = 8 }; DPCMSample currentDpcm = null; int dpcmWriteIdx = 0; Song song = null; string patternName = ""; string projectName; var lines = File.ReadAllLines(filename); for (int i = 0; i < lines.Length; i++) { var line = lines[i].Trim(); if (line.StartsWith("TITLE")) { projectName = line.Substring(5).Trim(' ', '"'); } else if (line.StartsWith("MACRO")) { var halves = line.Substring(5).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]); if (type < 3) { var env = new Envelope(); env.Length = curve.Length; env.Loop = loop; for (int j = 0; j < curve.Length; j++) { env.Values[j] = sbyte.Parse(curve[j]); } if (type == 2) { env.ConvertToAbsolute(); } envelopes[type][idx] = env; } else if (type == 4) { duties[idx] = int.Parse(curve[0]); } } else if (line.StartsWith("DPCMDEF")) { var param = SplitStringKeepQuotes(line.Substring(7)); var name = param[2]; var j = 2; while (!project.IsDPCMSampleNameUnique(name)) { name = param[2] + "-" + j++; } currentDpcm = project.CreateDPCMSample(name, new byte[int.Parse(param[1])]); dpcms[int.Parse(param[0])] = currentDpcm; dpcmWriteIdx = 0; } else if (line.StartsWith("DPCM")) { 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 - 11; if (note >= Note.NoteMin && note <= Note.NoteMax) { 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")) { var param = SplitStringKeepQuotes(line.Substring(8)); 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 name = param[6]; var j = 2; if (!project.IsInstrumentNameUnique(name)) { name = param[6] + "-" + j++; } var instrument = project.CreateInstrument(name); if (vol >= 0) { instrument.Envelopes[0] = envelopes[0][vol].Clone(); } if (arp >= 0) { instrument.Envelopes[1] = envelopes[1][arp].Clone(); } if (pit >= 0) { instrument.Envelopes[2] = envelopes[2][pit].Clone(); } if (dut >= 0) { instrument.DutyCycle = duties[dut]; } instruments[idx] = instrument; } else if (line.StartsWith("TRACK")) { var param = SplitStringKeepQuotes(line.Substring(5)); song = project.CreateSong(param[3]); song.Length = 0; song.PatternLength = int.Parse(param[0]); song.Speed = int.Parse(param[1]); song.Tempo = int.Parse(param[2]); } else if (line.StartsWith("COLUMNS")) { var param = line.Substring(7).Split(new[] { ' ', ':' }, StringSplitOptions.RemoveEmptyEntries); for (int j = 0; j < 5; 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[5]; for (int j = 0; j < 5; 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.Length++; } else if (line.StartsWith("PATTERN")) { patternName = line.Substring(8); } else if (line.StartsWith("ROW")) { var channels = line.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries); var rowIdx = Convert.ToInt32(channels[0].Substring(4, 2), 16); for (int j = 1; j <= 5; j++) { var noteData = channels[j].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); var pattern = song.Channels[j - 1].GetPattern(patternName); if (pattern == null) { continue; } if (noteData[0] == "---") { pattern.Notes[rowIdx].Value = 0; } else if (noteData[0] != "...") { int famitoneNote; if (j == 4) { famitoneNote = (Convert.ToInt32(noteData[0].Substring(0, 1), 16) + 31) + 1; } else { int semitone = noteLookup[noteData[0].Substring(0, 2)]; int octave = noteData[0][2] - '0'; famitoneNote = octave * 12 + semitone - 11; } if (famitoneNote > 0 && famitoneNote < 64) { pattern.Notes[rowIdx].Value = (byte)famitoneNote; pattern.Notes[rowIdx].Instrument = j == 5 ? null : instruments[Convert.ToInt32(noteData[1], 16)]; } else { // Note outside of range. } } // Read FX. for (int k = 0; k < columns[j - 1]; k++) { string fx = noteData[3 + k]; switch (fx[0]) { case 'B': // Jump pattern.Notes[rowIdx].Effect = Note.EffectJump; break; case 'D': // Skip pattern.Notes[rowIdx].Effect = Note.EffectSkip; break; case 'F': // Tempo pattern.Notes[rowIdx].Effect = Note.EffectSpeed; break; default: continue; } pattern.Notes[rowIdx].EffectParam = Convert.ToByte(fx.Substring(1), 16); } } } } foreach (var s in project.Songs) { s.RemoveEmptyPatterns(); foreach (var c in s.Channels) { c.ColorizePatterns(); } } return(project); }
public Project Load(string filename) { try { var lines = File.ReadAllLines(filename); var parameters = new Dictionary <string, string>(); var project = (Project)null; var instrument = (Instrument)null; var song = (Song)null; var channel = (Channel)null; var pattern = (Pattern)null; foreach (var line in lines) { var cmd = SplitLine(line.Trim(), ref parameters); switch (cmd) { case "Project": { project = new Project(); 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("Expansion", out var expansion)) { project.SetExpansionAudio(Array.IndexOf(Project.ExpansionShortNames, expansion)); } if (parameters.TryGetValue("TempoMode", out var tempoMode)) { project.TempoMode = Array.IndexOf(Project.TempoModeNames, tempoMode); } if (parameters.TryGetValue("PAL", out var pal)) { project.PalMode = bool.Parse(pal); } 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); } project.CreateDPCMSample(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": { instrument = project.CreateInstrument(parameters.TryGetValue("Expansion", out _) ? project.ExpansionAudio : Project.ExpansionNone, parameters["Name"]); if (instrument.ExpansionType == Project.ExpansionFds) { if (parameters.TryGetValue("FdsWavePreset", out var wavPresetStr)) { instrument.FdsWavePreset = (byte)Array.IndexOf(Envelope.PresetNames, wavPresetStr); } if (parameters.TryGetValue("FdsModPreset", out var modPresetStr)) { instrument.FdsWavePreset = (byte)Array.IndexOf(Envelope.PresetNames, 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.ExpansionType == Project.ExpansionN163) { if (parameters.TryGetValue("N163WavePreset", out var wavPresetStr)) { instrument.N163WavePreset = (byte)Array.IndexOf(Envelope.PresetNames, 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.ExpansionType == Project.ExpansionVrc7) { if (parameters.TryGetValue("Vrc7Patch", out var vrc7PatchStr)) { instrument.Vrc7Patch = byte.Parse(vrc7PatchStr); } if (instrument.Vrc7Patch == 0) { for (int i = 0; i < 8; i++) { if (parameters.TryGetValue($"Vrc7Reg{i}", out var regStr)) { instrument.Vrc7PatchRegs[i] = byte.Parse(regStr); } } } } break; } case "Envelope": { var env = instrument.Envelopes[Array.IndexOf(Envelope.EnvelopeShortNames, 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.SetBarLength(int.Parse(parameters["BarLength"])); 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"]); song.ResizeNotes(noteLength, false); song.SetBarLength(int.Parse(parameters["BarLength"]) * noteLength); song.SetDefaultPatternLength(int.Parse(parameters["PatternLength"]) * noteLength); } break; } case "PatternCustomSettings": { if (project.UsesFamiTrackerTempo) { song.SetPatternCustomSettings(int.Parse(parameters["Time"]), int.Parse(parameters["Length"])); } else { var patternLength = int.Parse(parameters["Length"]); var noteLength = int.Parse(parameters["NoteLength"]); var barLength = int.Parse(parameters["BarLength"]); song.SetPatternCustomSettings(int.Parse(parameters["Time"]), patternLength * noteLength, noteLength, barLength * noteLength); } break; } case "Channel": { var channelType = Array.IndexOf(Channel.ChannelExportNames, 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 (parameters.TryGetValue("Instrument", out var instStr)) { note.Instrument = project.GetInstrument(instStr); } if (parameters.TryGetValue("Attack", out var attackStr)) { note.HasAttack = bool.Parse(attackStr); } if (parameters.TryGetValue("Volume", out var volumeStr)) { note.Volume = byte.Parse(volumeStr); } if (parameters.TryGetValue("VibratoSpeed", out var vibSpeedStr)) { note.VibratoSpeed = byte.Parse(vibSpeedStr); } if (parameters.TryGetValue("VibratoDepth", out var vibDepthStr)) { note.VibratoDepth = byte.Parse(vibDepthStr); } if (parameters.TryGetValue("FinePitch", out var finePitchStr)) { note.FinePitch = sbyte.Parse(finePitchStr); } if (parameters.TryGetValue("SlideTarget", out var slideStr)) { note.SlideNoteTarget = (byte)Note.FromFriendlyName(valueStr); } if (parameters.TryGetValue("FdsModSpeed", out var modSpeedStr)) { note.FdsModSpeed = ushort.Parse(modSpeedStr); } if (parameters.TryGetValue("FdsModDepth", out var modDepthStr)) { note.FdsModDepth = byte.Parse(modDepthStr); } break; } case "PatternInstance": { var time = int.Parse(parameters["Time"]); channel.PatternInstances[time] = channel.GetPattern(parameters["Pattern"]); break; } } } return(project); } catch { return(null); } }
public static Project Load(string filename) { var project = new Project(); var envelopes = new Dictionary <int, Envelope> [Project.ExpansionCount, Envelope.Max]; var duties = new Dictionary <int, int> [Project.ExpansionCount]; var instruments = new Dictionary <int, Instrument>(); var dpcms = new Dictionary <int, DPCMSample>(); var columns = new int[5] { 1, 1, 1, 1, 1 }; var patternFxData = new Dictionary <Pattern, RowFxData[, ]>(); var noteLookup = new Dictionary <string, int> { ["A-"] = 9, ["A#"] = 10, ["B-"] = 11, ["C-"] = 0, ["C#"] = 1, ["D-"] = 2, ["D#"] = 3, ["E-"] = 4, ["F-"] = 5, ["F#"] = 6, ["G-"] = 7, ["G#"] = 8 }; 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>(); } } for (int i = 0; i < duties.Length; i++) { duties[i] = new Dictionary <int, int>(); } 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)); if (exp == 1) { project.SetExpansionAudio(Project.ExpansionVrc6); } } else if (line.StartsWith("MACRO")) { var expansion = line.Substring(5, 4) == "VRC6" ? Project.ExpansionVrc6 : 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]); if (type < 3) { var env = new Envelope(); env.Length = curve.Length; // FamiTracker allows envelope with release with no loop. We dont allow that. if (type == Envelope.Volume && loop == -1 && rel != -1) { loop = rel; } env.Loop = loop; env.Release = type == Envelope.Volume && rel != -1 ? rel + 1 : -1; for (int j = 0; j < curve.Length; j++) { env.Values[j] = sbyte.Parse(curve[j]); } if (type == 2) { env.Relative = true; } envelopes[expansion, type][idx] = env; } else if (type == 4) { duties[expansion][idx] = int.Parse(curve[0]); } } else if (line.StartsWith("DPCMDEF")) { var param = SplitStringKeepQuotes(line.Substring(7)); var name = param[2]; var j = 2; while (!project.IsDPCMSampleNameUnique(name)) { name = param[2] + "-" + j++; } currentDpcm = project.CreateDPCMSample(name, new byte[int.Parse(param[1])]); dpcms[int.Parse(param[0])] = currentDpcm; dpcmWriteIdx = 0; } else if (line.StartsWith("DPCM")) { 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")) { var expansion = line.Substring(4, 4) == "VRC6" ? Project.ExpansionVrc6 : 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 name = param[6]; var j = 2; if (!project.IsInstrumentNameUnique(name)) { name = param[6] + "-" + j++; } var expansionType = line.StartsWith("INSTVRC6") ? Project.ExpansionVrc6 : Project.ExpansionNone; var instrument = project.CreateInstrument(expansionType, name); if (vol >= 0) { instrument.Envelopes[0] = envelopes[expansion, 0][vol].Clone(); } if (arp >= 0) { instrument.Envelopes[1] = envelopes[expansion, 1][arp].Clone(); } if (pit >= 0) { instrument.Envelopes[2] = envelopes[expansion, 2][pit].Clone(); } if (dut >= 0) { instrument.DutyCycle = duties[expansionType][dut]; } instruments[idx] = instrument; } else if (line.StartsWith("TRACK")) { var param = SplitStringKeepQuotes(line.Substring(5)); song = project.CreateSong(param[3]); song.SetLength(0); song.SetPatternLength(int.Parse(param[0])); song.Speed = int.Parse(param[1]); song.Tempo = 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 rowIdx = 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.Notes[rowIdx].Value = Note.NoteStop; } else if (noteData[0] == "===") { pattern.Notes[rowIdx].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 = noteLookup[noteData[0].Substring(0, 2)]; int octave = noteData[0][2] - '0'; famitoneNote = octave * 12 + semitone + 1; } if (famitoneNote >= Note.MusicalNoteMin && famitoneNote <= Note.MusicalNoteMax) { pattern.Notes[rowIdx].Value = (byte)famitoneNote; pattern.Notes[rowIdx].Instrument = j == 5 ? null : instruments[Convert.ToInt32(noteData[1], 16)]; } else { // Note outside of range. } } // Volume if (noteData[2] != ".") { pattern.Notes[rowIdx].Volume = Convert.ToByte(noteData[2], 16); } // Read FX. for (int k = 0; k < columns[j - 1]; k++) { var fx = noteData[3 + k]; if (fx == "...") { continue; } var param = Convert.ToByte(fx.Substring(1), 16); var fxData = patternFxData[pattern]; fxData[rowIdx, k].fx = fx[0]; fxData[rowIdx, k].param = param; switch (fx[0]) { case 'B': // Jump pattern.Notes[rowIdx].Jump = param; break; case 'D': // Skip pattern.Notes[rowIdx].Skip = param; break; case 'F': // Tempo if (param <= 0x1f) // We only support speed change for now. { pattern.Notes[rowIdx].Speed = param; } break; case '4': // Vibrato pattern.Notes[rowIdx].VibratoDepth = (byte)(param & 0x0f); pattern.Notes[rowIdx].VibratoSpeed = (byte)VibratoSpeedImportLookup[param >> 4]; if (pattern.Notes[rowIdx].VibratoDepth == 0 || pattern.Notes[rowIdx].VibratoSpeed == 0) { pattern.Notes[rowIdx].Vibrato = 0; } break; } } } } } foreach (var s in project.Songs) { CreateSlideNotes(s, patternFxData); s.RemoveEmptyPatterns(); s.SetSensibleBarLength(); foreach (var c in s.Channels) { c.ColorizePatterns(); } } project.UpdateAllLastValidNotesAndVolume(); return(project); }
public Instrument CreateFromFile(Project project, string filename) { var bytes = System.IO.File.ReadAllBytes(filename); if (!bytes.Skip(0).Take(3).SequenceEqual(Encoding.ASCII.GetBytes("FTI"))) { Log.LogMessage(LogSeverity.Error, "Incompatible file."); return(null); } if (!bytes.Skip(3).Take(3).SequenceEqual(Encoding.ASCII.GetBytes("2.4"))) { Log.LogMessage(LogSeverity.Error, "Incompatible FTI version."); return(null); } var instType = InstrumentTypeLookup[bytes[6]]; // Needs to match the current expansion audio. Our enum happens to match (-1) for now. if (instType != Project.ExpansionNone && instType != project.ExpansionAudio) { Log.LogMessage(LogSeverity.Error, "Audio expansion does not match the current project expansion."); return(null); } var offset = 7; var nameLen = BitConverter.ToInt32(bytes, offset); offset += 4; var name = Encoding.ASCII.GetString(bytes, offset, nameLen); offset += nameLen; if (project.GetInstrument(name) != null) { Log.LogMessage(LogSeverity.Error, $"An instrument named '{name}' already exists."); return(null); } var instrument = project.CreateInstrument(instType, name); if (instType == Project.ExpansionFds) { var wavEnv = instrument.Envelopes[Envelope.FdsWaveform]; for (int i = 0; i < wavEnv.Length; i++) { wavEnv.Values[i] = (sbyte)bytes[offset++]; } var modEnv = instrument.Envelopes[Envelope.FdsModulation]; for (int i = 0; i < modEnv.Length; i++) { modEnv.Values[i] = (sbyte)bytes[offset++]; } instrument.FdsWavePreset = Envelope.WavePresetCustom; instrument.FdsModPreset = Envelope.WavePresetCustom; modEnv.ConvertFdsModulationToAbsolute(); // Skip mod speed/depth/delay. offset += sizeof(int) * 3; ReadEnvelope(bytes, ref offset, instrument, Envelope.Volume); ReadEnvelope(bytes, ref offset, instrument, Envelope.Arpeggio); ReadEnvelope(bytes, ref offset, instrument, Envelope.Pitch); } else if (instType == Project.ExpansionNone || instType == Project.ExpansionN163) { var seqCount = bytes[offset++]; if (seqCount != SEQ_COUNT) { Log.LogMessage(LogSeverity.Error, $"Unexpected number of envelopes ({seqCount})."); return(null); } // Envelopes for (int i = 0; i < SEQ_COUNT; i++) { if (bytes[offset++] == 1) { ReadEnvelope(bytes, ref offset, instrument, EnvelopeTypeLookup[i]); } } } else if (instType == Project.ExpansionVrc7) { instrument.Vrc7Patch = (byte)BitConverter.ToInt32(bytes, offset); offset += 4; if (instrument.Vrc7Patch == 0) { for (int i = 0; i < 8; ++i) { instrument.Vrc7PatchRegs[i] = bytes[offset++]; } } } if (instType == Project.ExpansionN163) { int waveSize = BitConverter.ToInt32(bytes, offset); offset += 4; int wavePos = BitConverter.ToInt32(bytes, offset); offset += 4; int waveCount = BitConverter.ToInt32(bytes, offset); offset += 4; instrument.N163WavePreset = Envelope.WavePresetCustom; instrument.N163WaveSize = (byte)waveSize; instrument.N163WavePos = (byte)wavePos; var wavEnv = instrument.Envelopes[Envelope.N163Waveform]; // Only read the first wave for now. for (int j = 0; j < waveSize; j++) { wavEnv.Values[j] = (sbyte)bytes[offset++]; } if (waveCount > 1) { Log.LogMessage(LogSeverity.Warning, $"Multiple N163 waveforms detected, only loading the first one."); } } // Samples if (instType == Project.ExpansionNone) { // Skip over the sample mappings for now, we will load them after the actual sample data. var assignedCount = BitConverter.ToInt32(bytes, offset); offset += 4; var mappingOffset = offset; offset += assignedCount * 4; var sampleCount = BitConverter.ToInt32(bytes, offset); offset += 4; var sampleMap = new DPCMSample[sampleCount]; for (int i = 0; i < sampleCount; i++) { var sampleIdx = BitConverter.ToInt32(bytes, offset); offset += 4; var sampleNameLen = BitConverter.ToInt32(bytes, offset); offset += 4; var sampleName = Encoding.ASCII.GetString(bytes, offset, sampleNameLen); offset += sampleNameLen; var sampleSize = BitConverter.ToInt32(bytes, offset); offset += 4; var sampleData = new byte[sampleSize]; Array.Copy(bytes, offset, sampleData, 0, sampleSize); offset += sampleSize; sampleMap[sampleIdx] = project.CreateDPCMSample(sampleName, sampleData); } for (int i = 0; i < assignedCount; i++) { byte idx = bytes[mappingOffset++]; byte sample = bytes[mappingOffset++]; byte pitch = bytes[mappingOffset++]; byte delta = bytes[mappingOffset++]; if (project.NoteSupportsDPCM(idx + 1)) { project.MapDPCMSample(idx + 1, sampleMap[sample - 1], pitch); } } } project.SortInstruments(); return(instrument); }