public static Note[] LoadNotes(Project project, bool createMissingInstruments, bool createMissingArpeggios, bool createMissingSamples)
        {
            var buffer = GetClipboardDataInternal(MagicNumberClipboardNotes);

            if (buffer == null)
            {
                return(null);
            }

            var serializer = new ProjectLoadBuffer(project, Compression.DecompressBytes(buffer, 4), Project.Version);

            LoadAndMergeSampleList(serializer, false, createMissingSamples);
            LoadAndMergeArpeggioList(serializer, false, createMissingArpeggios);
            LoadAndMergeInstrumentList(serializer, false, createMissingInstruments);

            int numNotes = 0;

            serializer.Serialize(ref numNotes);
            var notes = new Note[numNotes];

            for (int i = 0; i < numNotes; i++)
            {
                var note = new Note();
                note.SerializeState(serializer);

                if (!note.IsEmpty)
                {
                    notes[i] = note;
                }
            }

            project.SortInstruments();

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