public static VgmFile Load(Stream stream) { Stream readStream; if (IsVgmGzipped(stream)) { readStream = DecompressVgm(stream); } else { readStream = stream; } var file = new VgmFile(); using (var reader = new BinaryReader(readStream, Encoding.Unicode, true)) { file.Header = ReadVgmHeader(reader); uint gd3DataOffset = 0; if (file.Header.GD3Offset != 0) { // GD3 offset is relative to the header entry so add 0x14 to get absolute offset gd3DataOffset = file.Header.GD3Offset + 0x14; reader.BaseStream.Seek(gd3DataOffset, SeekOrigin.Begin); file.Gd3 = ReadGd3Tag(reader); } // for versions < 1.5 data offset is 0 and data starts at 0x40 uint vgmDataOffset = file.Header.VGMDataOffset > 0 ? file.Header.VGMDataOffset + 0x34 : 0x40; uint vgmDataLength; if (gd3DataOffset > vgmDataOffset) { vgmDataLength = gd3DataOffset - vgmDataOffset; } else { vgmDataLength = (file.Header.EofOffset + 4) - vgmDataOffset; } reader.BaseStream.Seek(vgmDataOffset, SeekOrigin.Begin); file.VgmData = reader.ReadBytes((int)vgmDataLength); // correct loop offset, calculate absolute offset and remove vgm offset if (file.Header.LoopOffset > 0) { file.LoopOffset = (file.Header.LoopOffset + 0x1C) - vgmDataOffset; } else { file.LoopOffset = 0; } } return(file); }
static void Main(string[] args) { _options = Options.ParseCommandLine(args); string vgmPath = _options.Filename; if (!File.Exists(vgmPath)) { Console.WriteLine($"File {vgmPath} not found."); return; } string vgmFileName = Path.GetFileNameWithoutExtension(vgmPath); VgmFile vgmFile; using (var fileStream = File.Open(vgmPath, FileMode.Open, FileAccess.Read)) { vgmFile = VgmFile.Load(fileStream); } if (_options.PrintVgmInfo) { Console.WriteLine($"Filename: {Path.GetFileName(vgmPath)}"); Console.WriteLine(); Console.WriteLine($"Track: {vgmFile.Gd3.English.TrackName}"); Console.WriteLine($"Game: {vgmFile.Gd3.English.GameName}"); Console.WriteLine($"System: {vgmFile.Gd3.English.SystemName}"); Console.WriteLine($"Composer: {vgmFile.Gd3.English.TrackAuthor}"); Console.WriteLine($"Release: {vgmFile.Gd3.ReleaseDate}"); Console.WriteLine($"VGM By: {vgmFile.Gd3.VgmAuthor}"); Console.WriteLine(); Console.WriteLine("Notes:"); Console.WriteLine(vgmFile.Gd3.Notes); Console.WriteLine(); } // generate the audio samples into a memory stream using (var writer = new WaveFileWriter(File.Open($"{vgmFileName}.wav", FileMode.Create), new WaveFormat(_sampleRate, 16, (ushort)(_options.MultiChannelOutput ? 4 : 2)))) { var psg = new SN76489(vgmFile.Header.SN76489ShiftRegisterWidth, vgmFile.Header.SN76489Feedback); _psgClocksPerSample = vgmFile.Header.SN76489Clock / (double)_sampleRate; bool playing = true; int loopLimit = 0; int loopCount = 0; uint playCursor = 0; while (playing) { byte command = vgmFile.VgmData[playCursor++]; switch (command) { // 0x50 dd: PSG(SN76489 / SN76496) write value dd case VgmCommand.PSGWriteDD: psg.WriteData(vgmFile.VgmData[playCursor++]); break; //0x61 nn nn : Wait n samples, n can range from 0 to 65535 (approx 1.49 // seconds). Longer pauses than this are represented by multiple // wait commands. case VgmCommand.WaitNSamples: int sampleCount = vgmFile.VgmData[playCursor++]; sampleCount |= vgmFile.VgmData[playCursor++] << 8; OutputPSGSamples(psg, writer, sampleCount); break; // 0x62: wait 735 samples(60th of a second), a shortcut for 0x61 0xdf 0x02 case VgmCommand.Wait735Samples: OutputPSGSamples(psg, writer, 735); break; // 0x63: wait 882 samples(50th of a second), a shortcut for 0x61 0x72 0x03 case VgmCommand.Wait882Samples: OutputPSGSamples(psg, writer, 882); break; // 0x66: end of sound data case VgmCommand.EndOfSoundData: if (vgmFile.LoopOffset != 0 && loopCount < loopLimit) { playCursor = vgmFile.LoopOffset; loopCount++; } else { playing = false; } break; // 0x7n: wait n+1 samples, n can range from 0 to 15. case VgmCommand.Wait01Samples: case VgmCommand.Wait02Samples: case VgmCommand.Wait03Samples: case VgmCommand.Wait04Samples: case VgmCommand.Wait05Samples: case VgmCommand.Wait06Samples: case VgmCommand.Wait07Samples: case VgmCommand.Wait08Samples: case VgmCommand.Wait09Samples: case VgmCommand.Wait10Samples: case VgmCommand.Wait11Samples: case VgmCommand.Wait12Samples: case VgmCommand.Wait13Samples: case VgmCommand.Wait14Samples: case VgmCommand.Wait15Samples: case VgmCommand.Wait16Samples: OutputPSGSamples(psg, writer, (command & 0x0F) + 1); break; // game gear stereo shift, ignore for now case 0x4F: int stereoByte = vgmFile.VgmData[playCursor++]; Console.WriteLine($"GG Stereo Write: 0x{stereoByte:X2}"); break; default: Console.Error.WriteLine("Unknown command: 0x{0:X2} at 0x{1:X4}", command, playCursor - 1); playing = false; break; } } } if (Debugger.IsAttached) { Console.Write("Press any key to continue..."); Console.ReadKey(true); } }