Example #1
0
        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);
        }
Example #2
0
        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);
            }
        }