예제 #1
0
        static void OutputPSGSamples(SN76489 psg, WaveFileWriter writer, int sampleCount)
        {
            for (int i = 0; i < sampleCount; i++)
            {
                _psgUpdateClock += _psgClocksPerSample;

                uint updateCycles = (uint)_psgUpdateClock; // truncate to get integer clock value, keeping the decimal part to accumulate

                psg.Update(updateCycles);

                if (!_options.MultiChannelOutput)
                {
                    var(left, right) = psg.Output;
                    writer.WriteSample(left);
                    writer.WriteSample(right);
                }
                else
                {
                    writer.WriteSample(psg.Channel0Output);
                    writer.WriteSample(psg.Channel1Output);
                    writer.WriteSample(psg.Channel2Output);
                    writer.WriteSample(psg.Channel3Output);
                }

                _psgUpdateClock -= updateCycles;
            }
        }
예제 #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);
            }
        }