예제 #1
0
        public void Parse(string filePath, AKAOType type, long limit = long.MaxValue)
        {
            PreParse(filePath);
            _type = type;
            if (FileSize < 4)
            {
                return;
            }
            Parse(buffer, type, limit);

            buffer.Close();
            fileStream.Close();
        }
예제 #2
0
        public void Parse(BinaryReader buffer, AKAOType type, long limit = long.MaxValue)
        {
            _type = type;
            if (buffer.BaseStream.Length < 4)
            {
                return;
            }

            if (_type == AKAOType.UNKNOWN)
            {
                // we must try to find the AKAO type
                byte[] header = buffer.ReadBytes(4);       // AKAO
                if (!CheckHeader(header))
                {
                    return;
                }
                ushort v1 = buffer.ReadUInt16(); // ID or type
                ushort v2 = buffer.ReadUInt16(); // File Length or empty
                byte   v3 = buffer.ReadByte();   // Type or empty
                byte   v4 = buffer.ReadByte();   // Type var or empty

                if (v2 + v3 + v4 == 0)
                {
                    if (v1 == 0)
                    {
                        _type = AKAOType.SAMPLE;
                    }
                    else
                    {
                        if (buffer.BaseStream.Position == 10)
                        {
                            // v1 is the sample collection ID in this case
                            _type = AKAOType.SOUND;
                        }
                        else
                        {
                            // E075.P have an AKAO PROG without v3 = 0xC8...
                            _type = AKAOType.PROG;
                        }
                    }
                }
                else if (v3 == 0xC8)
                {
                    _type = AKAOType.EFFECT;
                }
                else if (v2 > 0 && v2 == buffer.BaseStream.Length)
                {
                    _type = AKAOType.MUSIC;
                }

                buffer.BaseStream.Position -= 10;
            }


            switch (_type)
            {
            case AKAOType.MUSIC:
                //https://github.com/vgmtrans/vgmtrans/blob/master/src/main/formats/AkaoSeq.cpp
                // header datas
                byte[] header  = buffer.ReadBytes(4);   // AKAO
                ushort fileId  = buffer.ReadUInt16();
                ushort byteLen = buffer.ReadUInt16();
                ushort reverb  = buffer.ReadUInt16(); // 0x0500  | Just on case 0x0400 (MUSIC000.DAT), maybe it refer to the WAVE0005.DAT sample collection
                buffer.ReadBytes(6);                  // padding

                uint unk1      = buffer.ReadUInt32(); // never > 127, maybe a general volume ? or something like that
                uint sampleSet = buffer.ReadUInt32(); // ID of the WAVE*.DAT in the SOUND folder
                buffer.ReadBytes(8);                  // padding

                int   bnumt    = buffer.ReadInt32();
                uint  numTrack = ToolBox.GetNumPositiveBits(bnumt);
                short unk3     = buffer.ReadInt16(); // (0, -1, 255 or 16383) from MUSIC050 to MUSIC101 => unk3 != 0
                // when != 0 it seems like it's not a "music" but more like a sounds store for maps ambiance or monsters
                // when != 0, in most of case there is no instruments set nor a drum (excp 68, 69) and Unknowns AKAO events occur a lot.
                // in these cases you really feal that it doesn't really make sence to out a midi file and less a wav...
                // when != 0 => sampleSet 17 to 25
                // Case 255 (66 to 73, 82, 83, 96, 97)
                // Case 16383 (78 to 81, 88 to 91)
                // Case -1 all others from 50 to 101
                buffer.ReadBytes(10);                   // padding

                uint ptr1 = buffer.ReadUInt32() + 0x30; // instruments pointer
                uint ptr2 = buffer.ReadUInt32() + 0x34; // Drums pointer
                buffer.ReadBytes(8);                    // padding

                ushort jump        = buffer.ReadUInt16();
                long   basePtr     = buffer.BaseStream.Position;
                long   musInstrPtr = buffer.BaseStream.Position + jump - 2;


                if (true)
                {
                    Debug.Log(string.Concat("AKAO from : ", FileName, " FileSize = ", FileSize, "    |    reverb : ", reverb, " numTrack : ", numTrack, " sampleSet : ", sampleSet,
                                            "\r\ninstruments at : ", ptr1, "  Drums at : ", ptr2, "   musInstrPtr : ", musInstrPtr, "   |  unk1 : ", unk1, "   unk3 : ", unk3));
                }
                ushort[] tracksPtr = new ushort[numTrack];
                tracksPtr[0] = (ushort)musInstrPtr;

                for (uint i = 0; i < numTrack - 1; i++)
                {
                    tracksPtr[i + 1] = (ushort)((basePtr + i * 2) + buffer.ReadUInt16());
                }



                // music instuctions begin here, MIDI like format, we don't care yet, so let's jump



                uint instrCount = 0;
                // Instruments
                if (ptr1 > 0x30)
                {
                    buffer.BaseStream.Position = ptr1;
                    // Instruments Header always 0x20 ?
                    List <ushort> instrPtrs = new List <ushort>();
                    for (int i = 0; i < 0x10; i++)
                    {
                        ushort instrPtr = buffer.ReadUInt16();
                        if (instrPtr != 0xFFFF)
                        {
                            instrPtrs.Add(instrPtr);
                        }
                        else
                        {
                            // Padding
                        }
                    }

                    instrCount = (uint)instrPtrs.Count;
                    if (ptr2 > 0x34)
                    {
                        instrCount++;
                    }



                    if (UseDebug)
                    {
                        Debug.Log("Instruments number : " + instrCount);
                    }

                    instruments = new List <AKAOInstrument>();

                    for (int i = 0; i < instrPtrs.Count; i++)
                    {
                        AKAOInstrument instrument = new AKAOInstrument((uint)i, AKAOInstrument.InstrumentType.INSTR_MELODIC);
                        instrument.name = "Instrument #" + (ushort)i;
                        long instrStart = ptr1 + 0x20 + instrPtrs[i];
                        long instrEnd;
                        if (i < instrPtrs.Count - 1)
                        {
                            instrEnd = ptr1 + 0x20 + instrPtrs[i + 1];
                        }
                        else
                        {
                            if (ptr2 > 0x34)
                            {
                                instrEnd = ptr2;
                            }
                            else
                            {
                                instrEnd = byteLen;
                            }
                        }
                        int instrRegLoop = (int)(instrEnd - instrStart) / 0x08;
                        if (UseDebug)
                        {
                            Debug.Log(string.Concat("Instrument #", i, "   Regions count : ", instrRegLoop - 1));
                        }

                        instrument.regions = new AKAORegion[instrRegLoop - 1];     // -1 because the last 8 bytes are padding
                        for (int j = 0; j < instrRegLoop - 1; j++)
                        {
                            AKAORegion reg = new AKAORegion();
                            reg.FeedMelodic(buffer.ReadBytes(8));
                            instrument.regions[j] = reg;
                            if (UseDebug)
                            {
                                Debug.Log(reg.ToString());
                            }
                        }
                        buffer.ReadBytes(8);    // 0000 0000 0000 0000 padding

                        instruments.Add(instrument);
                    }
                }

                // Drum
                if (ptr2 > 0x34)
                {
                    if (buffer.BaseStream.Position != ptr2)
                    {
                        buffer.BaseStream.Position = ptr2;
                    }

                    // Special case when there is no melodic instruments
                    if (instruments == null)
                    {
                        instrCount++;
                        instruments = new List <AKAOInstrument>();
                    }

                    AKAOInstrument drum = new AKAOInstrument(instrCount - 1, AKAOInstrument.InstrumentType.INSTR_DRUM);
                    drum.name = "Drum";
                    int drumRegLoop = (int)(byteLen - ptr2) / 0x08;
                    if (UseDebug)
                    {
                        Debug.Log(string.Concat("Drum   Regions count : ", drumRegLoop - 1));
                    }

                    List <AKAORegion> dr = new List <AKAORegion>();
                    for (int j = 0; j < drumRegLoop - 1; j++)
                    {
                        byte[] b = buffer.ReadBytes(8);
                        if (b[0] == 0xFF && b[1] == 0xFF && b[2] == 0xFF && b[3] == 0xFF && b[4] == 0xFF && b[5] == 0xFF && b[6] == 0xFF && b[7] == 0xFF)
                        {
                            break;
                        }
                        if (b[0] > 0 && b[1] > 0 && b[6] > 0 && b[7] > 0)
                        {
                            AKAORegion dregion = new AKAORegion();
                            dregion.FeedDrum(b, j);
                            dr.Add(dregion);
                            if (UseDebug)
                            {
                                Debug.Log(dregion.ToString());
                            }
                        }
                    }

                    drum.regions = dr.ToArray();
                    instruments.Add(drum);
                }

                long end = 0;
                if (ptr1 > 0x30)
                {
                    end = ptr1;
                }
                else if (ptr2 > 0x34)
                {
                    end = ptr2;
                }
                else
                {
                    end = buffer.BaseStream.Length;
                }

                composer = new AKAOComposer(buffer, musInstrPtr, end, instrCount, numTrack, tracksPtr, FileName, true);


                // AKAO from : WAVE0000 startingArticulationId = 0
                // AKAO from : WAVE0005 startingArticulationId = 32
                // AKAO from : WAVE0032 startingArticulationId = 32
                // All other            startingArticulationId = 64
                // AKAO from : WAVE0200 startingArticulationId = 128

                // So we seek for the appropriate WAVE*.DAT in the SOUND folder
                string[] hash = FilePath.Split("/"[0]);
                hash[hash.Length - 2] = "SOUND";

                AKAO[] sampleCollections = new AKAO[3];

                // Program from 32 to 63
                hash[hash.Length - 1] = "WAVE0005.DAT";     // wave 005 or wave 032 ? (5 seems good)
                AKAO SampleColl32 = new AKAO();
                SampleColl32.UseDebug = UseDebug;
                SampleColl32.Parse(String.Join("/", hash), AKAO.SOUND);
                sampleCollections[0] = SampleColl32;

                string zz = "0";
                if (sampleSet < 100)
                {
                    zz += "0";
                }
                if (sampleSet < 10)
                {
                    zz += "0";
                }
                hash[hash.Length - 1] = "WAVE" + zz + sampleSet + ".DAT";
                string samplePath = String.Join("/", hash);
                bool   test       = File.Exists(samplePath);

                // Program from 64 to 95 or 127
                AKAO SampleColl64 = new AKAO();
                SampleColl64.UseDebug = UseDebug;
                SampleColl64.Parse(samplePath, AKAO.SOUND);
                sampleCollections[1] = SampleColl64;

                // Additionnal Collection, somztimes usefull for drum kit or A1 program change
                if (SampleColl64.articulations.Length < 64)
                {
                    hash[hash.Length - 1] = "WAVE0091.DAT";
                    AKAO addiColl = new AKAO();
                    addiColl.UseDebug = UseDebug;
                    addiColl.Parse(String.Join("/", hash), AKAO.SOUND);
                    sampleCollections[2] = addiColl;
                }

                if (composer.A1Calls.Count > 0)
                {
                    // we need to add new instruments with an unique region
                    foreach (uint iid in composer.A1Calls)
                    {
                        AKAOInstrument A1Instrument = new AKAOInstrument(iid);
                        A1Instrument.name    = "A1 Instrument #" + (ushort)iid;
                        A1Instrument.a1      = true;
                        A1Instrument.regions = new AKAORegion[1];
                        AKAORegion defaultRegion = new AKAORegion();
                        defaultRegion.articulationId = (byte)iid;
                        A1Instrument.regions[0]      = defaultRegion;
                        if (instruments == null)
                        {
                            instruments = new List <AKAOInstrument>();
                        }
                        instruments.Add(A1Instrument);
                    }
                }



                SF2 sf2 = null;
                if (bWAV || bSF2 || bDLS)
                {
                    sf2 = SoundFundry(this, sampleCollections);
                }
                if (bMID || bWAV)
                {
                    if (unk3 != 0)
                    {
                        bWAV = false;     // we don't want 1Gb crap .wav
                    }

                    composer.Synthetize(bMID, bWAV, sf2);
                }

                break;

            case AKAOType.SOUND:
                // Samples Collection

                // header datas
                header = buffer.ReadBytes(4); // AKAO
                ushort sampleId = buffer.ReadUInt16();
                buffer.ReadBytes(10);         // padding

                unk1 = buffer.ReadByte();     // almost always 0 (but one case 48 in WAVE0032)
                unk3 = buffer.ReadByte();     // almost always 81 (two cases 49 (WAVE0000, WAVE0005), one case 16 in WAVE0032, one case 177 in WAVE0200)
                buffer.ReadBytes(2);          // padding
                var sampleSize = buffer.ReadUInt32();
                startingArticulationId = buffer.ReadUInt32();
                var numArts = buffer.ReadUInt32();
                // mostly 32, sometimes 64, one case 48 (WAVE0071), one case 96 (WAVE0200)

                /* List of 64 arts
                 * WAVE0044
                 * WAVE0045
                 * WAVE0046
                 * WAVE0053
                 * WAVE0054
                 * WAVE0055
                 * WAVE0064
                 * WAVE0065
                 * WAVE0068
                 * WAVE0069
                 * WAVE0091
                 * WAVE0097
                 * WAVE0099
                 */

                buffer.ReadBytes(32);     // padding

                if (UseDebug)
                {
                    Debug.Log(string.Concat("AKAO from : ", FileName, " len = ", FileSize, "  ID : ", sampleId, " unk1 : ", unk1, " unk3 : ", unk3, " sampleSize : ", sampleSize, " stArtId : ", startingArticulationId, " numArts : ", numArts));
                }

                // Articulations section here
                articulations = new AKAOArticulation[numArts];
                for (uint i = 0; i < numArts; i++)
                {
                    AKAOArticulation arti = new AKAOArticulation(buffer, startingArticulationId + i);
                    articulations[i] = arti;
                    if (UseDebug)
                    {
                        //Debug.Log(arti.ToString());
                    }
                }

                // Samples section here
                ulong samStart = (ulong)buffer.BaseStream.Position;
                // First we need to determine the start and the end of the samples, 16 null bytes indicate a new sample, so lets find them.
                List <long> samPtr  = new List <long>();
                List <long> samEPtr = new List <long>();
                while (buffer.BaseStream.Position < buffer.BaseStream.Length)
                {
                    if (buffer.ReadUInt64() + buffer.ReadUInt64() == 0)
                    {
                        if (samPtr.Count > 0)
                        {
                            //samEPtr.Add(buffer.BaseStream.Position - 0x20);
                            samEPtr.Add(buffer.BaseStream.Position - 0x10);
                        }
                        samPtr.Add(buffer.BaseStream.Position - 0x10);
                        //samPtr.Add(buffer.BaseStream.Position);
                    }
                }
                samEPtr.Add(buffer.BaseStream.Length);

                // Let's loop again to get samples
                int numSam = samPtr.Count;
                samples = new AKAOSample[numSam];
                for (int i = 0; i < numSam; i++)
                {
                    buffer.BaseStream.Position = samPtr[i];
                    int        size = (int)(samEPtr[i] - samPtr[i]);
                    byte[]     dt   = buffer.ReadBytes(size);
                    AKAOSample sam  = new AKAOSample(string.Concat(FileName, " Sample #", (ushort)i), dt, (ulong)samPtr[i]);
                    sam.index  = i;
                    samples[i] = sam;

                    if (UseDebug && bWAV)
                    {
                        WAV wavSam = sam.ConvertToWAV();
                        wavSam.SetName(FileName + "_Sample_" + i);
                        ToolBox.DirExNorCreate(Application.dataPath + "/../Assets/Resources/Sounds/SampleColl/");
                        wavSam.WriteFile(Application.dataPath + "/../Assets/Resources/Sounds/SampleColl/" + FileName + "_Sample_" + i + ".wav", wavSam.Write());
                    }
                }
                // now to verify and associate each articulation with a sample index value
                // for every sample of every instrument, we add sample_section offset, because those values
                //  are relative to the beginning of the sample section
                for (uint i = 0; i < articulations.Length; i++)
                {
                    for (uint l = 0; l < samples.Length; l++)
                    {
                        //if (articulations[i].sampleOff + samStart + 0x10 == samples[l].offset)
                        if (articulations[i].sampleOff + samStart == samples[l].offset)
                        {
                            articulations[i].sampleNum = l;
                            articulations[i].sample    = samples[l];
                            samples[l].loopStart       = articulations[i].loopPt;

                            break;
                        }
                    }
                }
                break;

            case AKAOType.PROG:
                // unknown yet
                header = buffer.ReadBytes(4);    // AKAO
                ushort id = buffer.ReadUInt16();
                buffer.ReadUInt16();
                ushort tp = buffer.ReadUInt16();
                buffer.ReadUInt16();
                buffer.ReadUInt32();

                buffer.ReadUInt32();
                buffer.ReadUInt32();
                buffer.ReadUInt32();
                buffer.ReadUInt32();

                buffer.ReadUInt16();
                byteLen = buffer.ReadUInt16();

                //Debug.Log(string.Concat("AKAO PROG : id : ", id, "  tp : ", tp, "  byteLen : ", byteLen));

                //composer = new AKAOComposer(buffer, buffer.BaseStream.Position, limit, 0, 1, new ushort[] { (ushort)buffer.BaseStream.Position }, FileName, true);
                //composer.Synthetize(true, false);
                break;

            case AKAOType.EFFECT:
                //https://github.com/vgmtrans/vgmtrans/blob/akao-ps1/src/main/formats/AkaoSeq.cpp
                header   = buffer.ReadBytes(4);  // AKAO
                id       = buffer.ReadUInt16();
                byteLen  = buffer.ReadUInt16();
                reverb   = buffer.ReadUInt16();
                bnumt    = buffer.ReadInt32();
                numTrack = ToolBox.GetNumPositiveBits(bnumt);

                buffer.ReadBytes(6);

                Debug.Log(string.Concat("AKAO EFFECT : id : ", id, "  byteLen : ", byteLen, "  reverb : ", reverb, "  numTrack : ", numTrack));

                composer = new AKAOComposer(buffer, buffer.BaseStream.Position, limit, 0, 1, new ushort[] { (ushort)buffer.BaseStream.Position }, FileName, true);
                Debug.Log(string.Concat("composer.A1Calls.Count : ", composer.A1Calls.Count));

                composer.Synthetize(true, false);
                break;

            case AKAOType.SAMPLE:
                // similar to AKAOType.SOUND without articulations, we can output a WAV file
                // header datas
                header = buffer.ReadBytes(4); // AKAO
                buffer.ReadUInt16();
                buffer.ReadBytes(10);         // padding

                buffer.ReadUInt16();          //
                buffer.ReadBytes(2);          // padding
                buffer.ReadUInt32();
                buffer.ReadUInt32();
                buffer.ReadUInt32();

                buffer.ReadBytes(32);     // padding
                buffer.ReadBytes(16);     // sample padding

                AKAOSample sample = new AKAOSample(FileName, buffer.ReadBytes((int)(limit - buffer.BaseStream.Position)), (ulong)buffer.BaseStream.Position);
                WAV        nw     = sample.ConvertToWAV();
                nw.SetName(FileName);

                if (UseDebug && bWAV)
                {
                    ToolBox.DirExNorCreate(Application.dataPath + "/../Assets/Resources/Sounds/Effects/");
                    nw.WriteFile(Application.dataPath + "/../Assets/Resources/Sounds/Effects/" + FileName + ".wav", nw.Write());
                }
                break;
            }
        }