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