Ejemplo n.º 1
0
        public void Serialize(ref DPCMSample sample)
        {
            int sampleId = -1;

            Serialize(ref sampleId, true);
            sample = project.GetSample(sampleId);
        }
Ejemplo n.º 2
0
 static public ParamInfo[] GetParams(DPCMSample sample)
 {
     return(new[]
     {
         new DPCMSampleParamInfo(sample, "Preview Rate", 0, 15, 15, "Rate to use when previewing the processed\nDMC data with the play button above", true)
         {
             GetValue = () => { return sample.PreviewRate; }, GetValueString = () => { return DPCMSampleRate.GetString(true, FamiStudio.StaticInstance.PalPlayback, true, false, sample.PreviewRate); }, SetValue = (v) => { sample.PreviewRate = (byte)v; }
         },
         new DPCMSampleParamInfo(sample, "Sample Rate", 0, 15, 15, "Rate at which to resample the source data at", true)
         {
             GetValue = () => { return sample.SampleRate; }, GetValueString = () => { return DPCMSampleRate.GetString(true, FamiStudio.StaticInstance.PalPlayback, true, false, sample.SampleRate); }, SetValue = (v) => { sample.SampleRate = (byte)v; sample.Process(); }
         },
         new DPCMSampleParamInfo(sample, "Padding Mode", 0, 4, DPCMPaddingType.PadTo16Bytes, "Padding method for the processed DMC data", true)
         {
             GetValue = () => { return sample.PaddingMode; }, GetValueString = () => { return DPCMPaddingType.Names[sample.PaddingMode]; }, SetValue = (v) => { sample.PaddingMode = v; sample.Process(); }
         },
         new DPCMSampleParamInfo(sample, "DMC Initial Value", 0, 63, NesApu.DACDefaultValueDiv2, "Initial value of the DMC counter before any volume adjustment.\nThis is actually half of the value used in hardware.")
         {
             GetValue = () => { return sample.DmcInitialValueDiv2; }, SetValue = (v) => { sample.DmcInitialValueDiv2 = v; sample.Process(); }
         },
         new DPCMSampleParamInfo(sample, "Volume Adjust", 0, 200, 100, "Volume adjustment (%)")
         {
             GetValue = () => { return sample.VolumeAdjust; }, SetValue = (v) => { sample.VolumeAdjust = v; sample.Process(); }
         },
         new DPCMSampleParamInfo(sample, "Fine Tuning", 0, 200, 100, "Very fine pitch adjustment to help tune notes")
         {
             GetValue = () => { return (int)Math.Round((sample.FinePitch - 0.95f) * 2000); }, SetValue = (v) => { sample.FinePitch = (v / 2000.0f) + 0.95f; sample.Process(); }, GetValueString = () => { return (sample.FinePitch * 100.0f).ToString("N2") + "%"; }
         },
         new DPCMSampleParamInfo(sample, "Process as PAL", 0, 1, 0, "Use PAL sample rates for all processing\nFor DMC source data, assumes PAL sample rate")
         {
             GetValue = () => { return sample.PalProcessing ? 1 : 0; }, SetValue = (v) => { sample.PalProcessing = v != 0; sample.Process(); }
         },
         new DPCMSampleParamInfo(sample, "Trim Zero Volume", 0, 1, 0, "Trim parts of the source data that is considered too low to be audible")
         {
             GetValue = () => { return sample.TrimZeroVolume ? 1 : 0; }, SetValue = (v) => { sample.TrimZeroVolume = v != 0; sample.Process(); }
         },
         new DPCMSampleParamInfo(sample, "Reverse Bits", 0, 1, 0, "For DMC source data only, reverse the bits to correct errors in some NES games")
         {
             GetValue = () => { return !sample.SourceDataIsWav && sample.ReverseBits ? 1 : 0; }, SetValue = (v) => { if (!sample.SourceDataIsWav)
                                                                                                                     {
                                                                                                                         sample.ReverseBits = v != 0; sample.Process();
                                                                                                                     }
             }, IsEnabled = () => { return !sample.SourceDataIsWav; }
         }
     });
 }
Ejemplo n.º 3
0
 static public ParamInfo[] GetParams(DPCMSample sample)
 {
     return(new[]
     {
         new DPCMSampleParamInfo(sample, "Preview Rate", 0, 15, 15, "Rate to use when previewing the processed\nDMC data with the play button above", true)
         {
             GetValue = () => { return sample.PreviewRate; }, GetValueString = () => { return DPCMSampleRate.Strings[FamiStudio.StaticInstance.PalPlayback ? 1 : 0][sample.PreviewRate]; }, SetValue = (v) => { sample.PreviewRate = (byte)v; }
         },
         new DPCMSampleParamInfo(sample, "Sample Rate", 0, 15, 15, "Rate at which to resample the source data at", true)
         {
             GetValue = () => { return sample.SampleRate; }, GetValueString = () => { return DPCMSampleRate.Strings[sample.PalProcessing ? 1 : 0][sample.SampleRate]; }, SetValue = (v) => { sample.SampleRate = (byte)v; sample.Process(); }
         },
         new DPCMSampleParamInfo(sample, "Padding Mode", 0, 4, DPCMPaddingType.PadTo16Bytes, "Padding method for the processed DMC data", true)
         {
             GetValue = () => { return sample.PaddingMode; }, GetValueString = () => { return DPCMPaddingType.Names[sample.PaddingMode]; }, SetValue = (v) => { sample.PaddingMode = v; sample.Process(); }
         },
         new DPCMSampleParamInfo(sample, "Volume Adjust", 0, 200, 100, "Volume adjustment (%)")
         {
             GetValue = () => { return sample.VolumeAdjust; }, SetValue = (v) => { sample.VolumeAdjust = v; sample.Process(); }
         },
         // new DPCMSampleParamInfo(sample, "Low-pass filter", 0, 100, 0, "Low-pass filter") // Not satisfied with this, disabling.
         //    { GetValue = () => { return sample.LowPassFilter; }, SetValue = (v) => { sample.LowPassFilter = v; sample.Process(); } },
         new DPCMSampleParamInfo(sample, "Process as PAL", 0, 1, 0, "Use PAL sample rates for all processing\nFor DMC source data, assumes PAL sample rate")
         {
             GetValue = () => { return sample.PalProcessing ? 1 : 0; }, SetValue = (v) => { sample.PalProcessing = v != 0; sample.Process(); }
         },
         new DPCMSampleParamInfo(sample, "Trim Zero Volume", 0, 1, 0, "Trim parts of the source data that is considered too low to be audible")
         {
             GetValue = () => { return sample.TrimZeroVolume ? 1 : 0; }, SetValue = (v) => { sample.TrimZeroVolume = v != 0; sample.Process(); }
         },
         new DPCMSampleParamInfo(sample, "Reverse Bits", 0, 1, 0, "For DMC source data only, reverse the bits to correct errors in some NES games")
         {
             GetValue = () => { return !sample.SourceDataIsWav && sample.ReverseBits ? 1 : 0; }, SetValue = (v) => { if (!sample.SourceDataIsWav)
                                                                                                                     {
                                                                                                                         sample.ReverseBits = v != 0; sample.Process();
                                                                                                                     }
             }, IsEnabled = () => { return !sample.SourceDataIsWav; }
         }
     });
 }
Ejemplo n.º 4
0
        public void Serialize(ref DPCMSample sample)
        {
            int sampleId = sample == null ? -1 : sample.Id;

            Serialize(ref sampleId);
        }
Ejemplo n.º 5
0
        private static bool LoadAndMergeSampleList(ProjectLoadBuffer serializer, bool checkOnly = false, bool createMissing = true)
        {
            int numSamples = 0;

            serializer.Serialize(ref numSamples);

            bool needMerge   = false;
            var  dummySample = new DPCMSample();

            for (int i = 0; i < numSamples; i++)
            {
                int    sampleId   = 0;
                string sampleName = "";

                serializer.Serialize(ref sampleId);
                serializer.Serialize(ref sampleName);

                var existingSample = serializer.Project.GetSample(sampleName);

                if (existingSample != null)
                {
                    serializer.RemapId(sampleId, existingSample.Id);
                    dummySample.SerializeState(serializer); // Skip
                }
                else
                {
                    needMerge = true;

                    if (!checkOnly && createMissing)
                    {
                        var sample = serializer.Project.CreateDPCMSample(sampleName);
                        serializer.RemapId(sampleId, sample.Id);
                        sample.SerializeState(serializer);
                    }
                    else
                    {
                        serializer.RemapId(sampleId, -1);
                        dummySample.SerializeState(serializer); // Skip
                    }
                }
            }

            int numMappings = 0;

            serializer.Serialize(ref numMappings);

            for (int i = 0; i < numMappings; i++)
            {
                int    note       = 0;
                string sampleName = "";

                serializer.Serialize(ref note);
                serializer.Serialize(ref sampleName);

                var mapping = new DPCMSampleMapping();
                mapping.SerializeState(serializer);

                if (serializer.Project.GetDPCMMapping(note) == null)
                {
                    needMerge = true;

                    if (!checkOnly && createMissing)
                    {
                        var sample = serializer.Project.GetSample(sampleName);

                        if (sample != null)
                        {
                            serializer.Project.MapDPCMSample(note, sample, mapping.Pitch, mapping.Loop);
                        }
                    }
                }
            }

            serializer.Project.SortSamples();

            return(needMerge);
        }
Ejemplo n.º 6
0
 public DPCMSampleParamInfo(DPCMSample sample, string name, int minVal, int maxVal, int defaultVal, string tooltip, bool list = false) :
     base(name, minVal, maxVal, defaultVal, tooltip, list)
 {
 }
        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 && 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 == 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++];

                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 == 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[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.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);
        }
Ejemplo n.º 8
0
        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);
        }
Ejemplo n.º 9
0
        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);
        }
Ejemplo n.º 10
0
        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);
        }
Ejemplo n.º 11
0
        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);
        }