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 != ExpansionType.None && !project.UsesExpansionAudio(instType)) { Log.LogMessage(LogSeverity.Error, "Audio expansion is not enabled in the current project."); 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 == ExpansionType.Fds) { var wavEnv = instrument.Envelopes[EnvelopeType.FdsWaveform]; for (int i = 0; i < wavEnv.Length; i++) { wavEnv.Values[i] = (sbyte)bytes[offset++]; } var modEnv = instrument.Envelopes[EnvelopeType.FdsModulation]; for (int i = 0; i < modEnv.Length; i++) { modEnv.Values[i] = (sbyte)bytes[offset++]; } instrument.FdsWavePreset = WavePresetType.Custom; instrument.FdsModPreset = WavePresetType.Custom; modEnv.ConvertFdsModulationToAbsolute(); // Skip mod speed/depth/delay. offset += sizeof(int) * 3; ReadEnvelope(bytes, ref offset, instrument, EnvelopeType.Volume); ReadEnvelope(bytes, ref offset, instrument, EnvelopeType.Arpeggio); ReadEnvelope(bytes, ref offset, instrument, EnvelopeType.Pitch); } else if (instType == ExpansionType.None || instType == ExpansionType.N163) { var seqCount = bytes[offset++]; // Envelopes for (int i = 0; i < seqCount; i++) { if (bytes[offset++] == 1) { ReadEnvelope(bytes, ref offset, instrument, EnvelopeTypeLookup[i]); } } } else if (instType == ExpansionType.Vrc7) { 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 == ExpansionType.N163) { 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 = WavePresetType.Custom; instrument.N163WaveSize = (byte)waveSize; instrument.N163WavePos = (byte)wavePos; var wavEnv = instrument.Envelopes[EnvelopeType.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 == ExpansionType.None) { // 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[MAX_DSAMPLES]; 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.CreateDPCMSampleFromDmcData(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 & 0x0f, (pitch & 0x80) != 0); } } } project.SortInstruments(); return(instrument); }