diff options
Diffstat (limited to '3rdparty/ymfm/examples/vgmrender/vgmrender.cpp')
-rw-r--r-- | 3rdparty/ymfm/examples/vgmrender/vgmrender.cpp | 1439 |
1 files changed, 1439 insertions, 0 deletions
diff --git a/3rdparty/ymfm/examples/vgmrender/vgmrender.cpp b/3rdparty/ymfm/examples/vgmrender/vgmrender.cpp new file mode 100644 index 00000000000..14b5bda29d6 --- /dev/null +++ b/3rdparty/ymfm/examples/vgmrender/vgmrender.cpp @@ -0,0 +1,1439 @@ +// +// Simple vgm renderer. +// +// Leverages em_inflate tiny inflater from https://github.com/emmanuel-marty/em_inflate +// +// Compile with: +// +// g++ --std=c++14 -I../../src vgmrender.cpp em_inflate.cpp ../../src/ymfm_misc.cpp ../../src/ymfm_opl.cpp ../../src/ymfm_opm.cpp ../../src/ymfm_opn.cpp ../../src/ymfm_adpcm.cpp ../../src/ymfm_pcm.cpp ../../src/ymfm_ssg.cpp -o vgmrender.exe +// +// or: +// +// clang++ --std=c++14 -I../../src vgmrender.cpp em_inflate.cpp ../../src/ymfm_misc.cpp ../../src/ymfm_opl.cpp ../../src/ymfm_opm.cpp ../../src/ymfm_opn.cpp ../../src/ymfm_adpcm.cpp ../../src/ymfm_pcm.cpp ../../src/ymfm_ssg.cpp -o vgmrender.exe +// +// or: +// +// cl -I..\..\src vgmrender.cpp em_inflate.cpp ..\..\src\ymfm_misc.cpp ..\..\src\ymfm_opl.cpp ..\..\src\ymfm_opm.cpp ..\..\src\ymfm_opn.cpp ..\..\src\ymfm_adpcm.cpp ..\..\src\ymfm_pcm.cpp ..\..\src\ymfm_ssg.cpp /Od /Zi /std:c++14 /EHsc +// + +#define _CRT_SECURE_NO_WARNINGS + +#include <cmath> +#include <cstdio> +#include <cstdint> +#include <cstring> +#include <list> +#include <string> + +#include "em_inflate.h" +#include "ymfm_misc.h" +#include "ymfm_opl.h" +#include "ymfm_opm.h" +#include "ymfm_opn.h" + +#define LOG_WRITES (0) + +// run this many dummy clocks of each chip before generating +#define EXTRA_CLOCKS (0) + + +// enable this to run the nuked OPN2 core in parallel; output is not captured, +// but logging can be added to observe behaviors +#define RUN_NUKED_OPN2 (0) +#if (RUN_NUKED_OPN2) +namespace nuked { +bool s_log_envelopes = false; +const int s_log_envelopes_channel = 5; +#include "test/ym3438.h" +} +#endif + +// enable this to capture each chip at its native rate as well +#define CAPTURE_NATIVE (0 || RUN_NUKED_OPN2) + + + +//********************************************************* +// GLOBAL TYPES +//********************************************************* + +// we use an int64_t as emulated time, as a 32.32 fixed point value +using emulated_time = int64_t; + +// enumeration of the different types of chips we support +enum chip_type +{ + CHIP_YM2149, + CHIP_YM2151, + CHIP_YM2203, + CHIP_YM2413, + CHIP_YM2608, + CHIP_YM2610, + CHIP_YM2612, + CHIP_YM3526, + CHIP_Y8950, + CHIP_YM3812, + CHIP_YMF262, + CHIP_YMF278B, + CHIP_TYPES +}; + + + +//********************************************************* +// CLASSES +//********************************************************* + +// ======================> vgm_chip_base + +// abstract base class for a Yamaha chip; we keep a list of these for processing +// as new commands come in +class vgm_chip_base +{ +public: + // construction + vgm_chip_base(uint32_t clock, chip_type type, char const *name) : + m_type(type), + m_name(name) + { + } + + // destruction + virtual ~vgm_chip_base() + { + } + + // simple getters + chip_type type() const { return m_type; } + virtual uint32_t sample_rate() const = 0; + + // required methods for derived classes to implement + virtual void write(uint32_t reg, uint8_t data) = 0; + virtual void generate(emulated_time output_start, emulated_time output_step, int32_t *buffer) = 0; + + // write data to the ADPCM-A buffer + void write_data(ymfm::access_class type, uint32_t base, uint32_t length, uint8_t const *src) + { + uint32_t end = base + length; + if (end > m_data[type].size()) + m_data[type].resize(end); + memcpy(&m_data[type][base], src, length); + } + + // seek within the PCM stream + void seek_pcm(uint32_t pos) { m_pcm_offset = pos; } + uint8_t read_pcm() { auto &pcm = m_data[ymfm::ACCESS_PCM]; return (m_pcm_offset < pcm.size()) ? pcm[m_pcm_offset++] : 0; } + +protected: + // internal state + chip_type m_type; + std::string m_name; + std::vector<uint8_t> m_data[ymfm::ACCESS_CLASSES]; + uint32_t m_pcm_offset; +#if (CAPTURE_NATIVE) +public: + std::vector<int32_t> m_native_data; +#endif +#if (RUN_NUKED_OPN2) +public: + nuked::ym3438_t *m_external = nullptr; + std::vector<int32_t> m_nuked_data; +#endif +}; + + +// ======================> vgm_chip + +// actual chip-specific implementation class; includes implementatino of the +// ymfm_interface as needed for vgmplay purposes +template<typename ChipType> +class vgm_chip : public vgm_chip_base, public ymfm::ymfm_interface +{ +public: + // construction + vgm_chip(uint32_t clock, chip_type type, char const *name) : + vgm_chip_base(clock, type, name), + m_chip(*this), + m_clock(clock), + m_clocks(0), + m_step(0x100000000ull / m_chip.sample_rate(clock)), + m_pos(0) + { + m_chip.reset(); + + for (int clock = 0; clock < EXTRA_CLOCKS; clock++) + m_chip.generate(&m_output); + +#if (RUN_NUKED_OPN2) + if (type == CHIP_YM2612) + { + m_external = new nuked::ym3438_t; + nuked::OPN2_SetChipType(nuked::ym3438_mode_ym2612); + nuked::OPN2_Reset(m_external); + nuked::Bit16s buffer[2]; + for (int clocks = 0; clocks < 24 * EXTRA_CLOCKS; clocks++) + nuked::OPN2_Clock(m_external, buffer); + } +#endif + } + + virtual uint32_t sample_rate() const override + { + return m_chip.sample_rate(m_clock); + } + + // handle a register write: just queue for now + virtual void write(uint32_t reg, uint8_t data) override + { + m_queue.push_back(std::make_pair(reg, data)); + } + + // generate one output sample of output + virtual void generate(emulated_time output_start, emulated_time output_step, int32_t *buffer) override + { + uint32_t addr1 = 0xffff, addr2 = 0xffff; + uint8_t data1 = 0, data2 = 0; + + // see if there is data to be written; if so, extract it and dequeue + if (!m_queue.empty()) + { + auto front = m_queue.front(); + addr1 = 0 + 2 * ((front.first >> 8) & 3); + data1 = front.first & 0xff; + addr2 = addr1 + ((m_type == CHIP_YM2149) ? 2 : 1); + data2 = front.second; + m_queue.erase(m_queue.begin()); + } + + // write to the chip + if (addr1 != 0xffff) + { + if (LOG_WRITES) + printf("%10.5f: %s %03X=%02X\n", double(output_start) / double(1LL << 32), m_name.c_str(), data1 + 0x100 * (addr1/2), data2); + m_chip.write(addr1, data1); + m_chip.write(addr2, data2); + } + + // generate at the appropriate sample rate +// nuked::s_log_envelopes = (output_start >= (22ll << 32) && output_start < (24ll << 32)); + for ( ; m_pos <= output_start; m_pos += m_step) + { + m_chip.generate(&m_output); + +#if (CAPTURE_NATIVE) + // if capturing native, append each generated sample + m_native_data.push_back(m_output.data[0]); + m_native_data.push_back(m_output.data[ChipType::OUTPUTS > 1 ? 1 : 0]); +#endif + +#if (RUN_NUKED_OPN2) + // if running nuked, capture its output as well + if (m_external != nullptr) + { + int32_t sum[2] = { 0 }; + if (addr1 != 0xffff) + nuked::OPN2_Write(m_external, addr1, data1); + nuked::Bit16s buffer[2]; + for (int clocks = 0; clocks < 12; clocks++) + { + nuked::OPN2_Clock(m_external, buffer); + sum[0] += buffer[0]; + sum[1] += buffer[1]; + } + if (addr2 != 0xffff) + nuked::OPN2_Write(m_external, addr2, data2); + for (int clocks = 0; clocks < 12; clocks++) + { + nuked::OPN2_Clock(m_external, buffer); + sum[0] += buffer[0]; + sum[1] += buffer[1]; + } + addr1 = addr2 = 0xffff; + m_nuked_data.push_back(sum[0] / 24); + m_nuked_data.push_back(sum[1] / 24); + } +#endif + } + + // add the final result to the buffer + if (m_type == CHIP_YM2203) + { + int32_t out0 = m_output.data[0]; + int32_t out1 = m_output.data[1 % ChipType::OUTPUTS]; + int32_t out2 = m_output.data[2 % ChipType::OUTPUTS]; + int32_t out3 = m_output.data[3 % ChipType::OUTPUTS]; + *buffer++ += out0 + out1 + out2 + out3; + *buffer++ += out0 + out1 + out2 + out3; + } + else if (m_type == CHIP_YM2608 || m_type == CHIP_YM2610) + { + int32_t out0 = m_output.data[0]; + int32_t out1 = m_output.data[1 % ChipType::OUTPUTS]; + int32_t out2 = m_output.data[2 % ChipType::OUTPUTS]; + *buffer++ += out0 + out2; + *buffer++ += out1 + out2; + } + else if (m_type == CHIP_YMF278B) + { + *buffer++ += m_output.data[4 % ChipType::OUTPUTS]; + *buffer++ += m_output.data[5 % ChipType::OUTPUTS]; + } + else if (ChipType::OUTPUTS == 1) + { + *buffer++ += m_output.data[0]; + *buffer++ += m_output.data[0]; + } + else + { + *buffer++ += m_output.data[0]; + *buffer++ += m_output.data[1 % ChipType::OUTPUTS]; + } + m_clocks++; + } + +protected: + // handle a read from the buffer + virtual uint8_t ymfm_external_read(ymfm::access_class type, uint32_t offset) override + { + auto &data = m_data[type]; + return (offset < data.size()) ? data[offset] : 0; + } + + // internal state + ChipType m_chip; + uint32_t m_clock; + uint64_t m_clocks; + typename ChipType::output_data m_output; + emulated_time m_step; + emulated_time m_pos; + std::vector<std::pair<uint32_t, uint8_t>> m_queue; +}; + + + +//********************************************************* +// GLOBAL HELPERS +//********************************************************* + +// global list of active chips +std::vector<std::unique_ptr<vgm_chip_base>> active_chips; + + +//------------------------------------------------- +// parse_uint32 - parse a little-endian uint32_t +//------------------------------------------------- + +uint32_t parse_uint32(std::vector<uint8_t> &buffer, uint32_t &offset) +{ + uint32_t result = buffer[offset++]; + result |= buffer[offset++] << 8; + result |= buffer[offset++] << 16; + result |= buffer[offset++] << 24; + return result; +} + + +//------------------------------------------------- +// add_chips - add 1 or 2 instances of the given +// supported chip type +//------------------------------------------------- + +template<typename ChipType> +void add_chips(uint32_t clock, chip_type type, char const *chipname) +{ + uint32_t clockval = clock & 0x3fffffff; + int numchips = (clock & 0x40000000) ? 2 : 1; + printf("Adding %s%s @ %dHz\n", (numchips == 2) ? "2 x " : "", chipname, clockval); + for (int index = 0; index < numchips; index++) + { + char name[100]; + sprintf(name, "%s #%d", chipname, index); + active_chips.push_back(std::make_unique<vgm_chip<ChipType>>(clockval, type, (numchips == 2) ? name : chipname)); + } + + if (type == CHIP_YM2608) + { + FILE *rom = fopen("ym2608_adpcm_rom.bin", "rb"); + if (rom == nullptr) + fprintf(stderr, "Warning: YM2608 enabled but ym2608_adpcm_rom.bin not found\n"); + else + { + fseek(rom, 0, SEEK_END); + uint32_t size = ftell(rom); + fseek(rom, 0, SEEK_SET); + std::vector<uint8_t> temp(size); + fread(&temp[0], 1, size, rom); + fclose(rom); + for (auto &chip : active_chips) + if (chip->type() == type) + chip->write_data(ymfm::ACCESS_ADPCM_A, 0, size, &temp[0]); + } + } +} + + +//------------------------------------------------- +// parse_header - parse the vgm header, adding +// chips for anything we encounter that we can +// support +//------------------------------------------------- + +uint32_t parse_header(std::vector<uint8_t> &buffer) +{ + // +00: already checked the ID + uint32_t offset = 4; + + // +04: parse the size + uint32_t size = parse_uint32(buffer, offset); + if (offset - 4 + size > buffer.size()) + { + fprintf(stderr, "Total size for file is too small; file may be truncated\n"); + size = buffer.size() - 4; + } + buffer.resize(size + 4); + + // +08: parse the version + uint32_t version = parse_uint32(buffer, offset); + if (version > 0x171) + fprintf(stderr, "Warning: version > 1.71 detected, some things may not work\n"); + + // +0C: SN76489 clock + uint32_t clock = parse_uint32(buffer, offset); + if (clock != 0) + fprintf(stderr, "Warning: clock for SN76489 specified (%d), but not supported\n", clock); + + // +10: YM2413 clock + clock = parse_uint32(buffer, offset); + if (clock != 0) + add_chips<ymfm::ym2413>(clock, CHIP_YM2413, "YM2413"); + + // +14: GD3 offset + uint32_t dummy = parse_uint32(buffer, offset); + + // +18: Total # samples + dummy = parse_uint32(buffer, offset); + + // +1C: Loop offset + dummy = parse_uint32(buffer, offset); + + // +20: Loop # samples + dummy = parse_uint32(buffer, offset); + + // +24: Rate + dummy = parse_uint32(buffer, offset); + + // +28: SN76489 feedback / SN76489 shift register width / SN76489 Flags + dummy = parse_uint32(buffer, offset); + + // +2C: YM2612 clock + clock = parse_uint32(buffer, offset); + if (version >= 0x110 && clock != 0) + add_chips<ymfm::ym2612>(clock, CHIP_YM2612, "YM2612"); + + // +30: YM2151 clock + clock = parse_uint32(buffer, offset); + if (version >= 0x110 && clock != 0) + add_chips<ymfm::ym2151>(clock, CHIP_YM2151, "YM2151"); + + // +34: VGM data offset + uint32_t data_start = parse_uint32(buffer, offset); + data_start += offset - 4; + if (version < 0x150) + data_start = 0x40; + + // +38: Sega PCM clock + clock = parse_uint32(buffer, offset); + if (version >= 0x151 && clock != 0) + fprintf(stderr, "Warning: clock for Sega PCM specified, but not supported\n"); + + // +3C: Sega PCM interface register + dummy = parse_uint32(buffer, offset); + + // +40: RF5C68 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x151 && clock != 0) + fprintf(stderr, "Warning: clock for RF5C68 specified, but not supported\n"); + + // +44: YM2203 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x151 && clock != 0) + add_chips<ymfm::ym2203>(clock, CHIP_YM2203, "YM2203"); + + // +48: YM2608 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x151 && clock != 0) + add_chips<ymfm::ym2608>(clock, CHIP_YM2608, "YM2608"); + + // +4C: YM2610/2610B clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x151 && clock != 0) + { + if (clock & 0x80000000) + add_chips<ymfm::ym2610b>(clock, CHIP_YM2610, "YM2610B"); + else + add_chips<ymfm::ym2610>(clock, CHIP_YM2610, "YM2610"); + } + + // +50: YM3812 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x151 && clock != 0) + add_chips<ymfm::ym3812>(clock, CHIP_YM3812, "YM3812"); + + // +54: YM3526 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x151 && clock != 0) + add_chips<ymfm::ym3526>(clock, CHIP_YM3526, "YM3526"); + + // +58: Y8950 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x151 && clock != 0) + add_chips<ymfm::y8950>(clock, CHIP_Y8950, "Y8950"); + + // +5C: YMF262 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x151 && clock != 0) + add_chips<ymfm::ymf262>(clock, CHIP_YMF262, "YMF262"); + + // +60: YMF278B clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x151 && clock != 0) + add_chips<ymfm::ymf278b>(clock, CHIP_YMF278B, "YMF278B"); + + // +64: YMF271 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x151 && clock != 0) + fprintf(stderr, "Warning: clock for YMF271 specified, but not supported\n"); + + // +68: YMF280B clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x151 && clock != 0) + fprintf(stderr, "Warning: clock for YMF280B specified, but not supported\n"); + + // +6C: RF5C164 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x151 && clock != 0) + fprintf(stderr, "Warning: clock for RF5C164 specified, but not supported\n"); + + // +70: PWM clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x151 && clock != 0) + fprintf(stderr, "Warning: clock for PWM specified, but not supported\n"); + + // +74: AY8910 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x151 && clock != 0) + { + fprintf(stderr, "Warning: clock for AY8910 specified, substituting YM2149\n"); + add_chips<ymfm::ym2149>(clock, CHIP_YM2149, "YM2149"); + } + + // +78: AY8910 flags + if (offset + 4 > data_start) + return data_start; + dummy = parse_uint32(buffer, offset); + + // +7C: volume / loop info + if (offset + 4 > data_start) + return data_start; + dummy = parse_uint32(buffer, offset); + if ((dummy & 0xff) != 0) + printf("Volume modifier: %02X (=%d)\n", dummy & 0xff, int(pow(2, double(dummy & 0xff) / 0x20))); + + // +80: GameBoy DMG clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x161 && clock != 0) + fprintf(stderr, "Warning: clock for GameBoy DMG specified, but not supported\n"); + + // +84: NES APU clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x161 && clock != 0) + fprintf(stderr, "Warning: clock for NES APU specified, but not supported\n"); + + // +88: MultiPCM clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x161 && clock != 0) + fprintf(stderr, "Warning: clock for MultiPCM specified, but not supported\n"); + + // +8C: uPD7759 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x161 && clock != 0) + fprintf(stderr, "Warning: clock for uPD7759 specified, but not supported\n"); + + // +90: OKIM6258 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x161 && clock != 0) + fprintf(stderr, "Warning: clock for OKIM6258 specified, but not supported\n"); + + // +94: OKIM6258 Flags / K054539 Flags / C140 Chip Type / reserved + if (offset + 4 > data_start) + return data_start; + dummy = parse_uint32(buffer, offset); + + // +98: OKIM6295 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x161 && clock != 0) + fprintf(stderr, "Warning: clock for OKIM6295 specified, but not supported\n"); + + // +9C: K051649 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x161 && clock != 0) + fprintf(stderr, "Warning: clock for K051649 specified, but not supported\n"); + + // +A0: K054539 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x161 && clock != 0) + fprintf(stderr, "Warning: clock for K054539 specified, but not supported\n"); + + // +A4: HuC6280 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x161 && clock != 0) + fprintf(stderr, "Warning: clock for HuC6280 specified, but not supported\n"); + + // +A8: C140 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x161 && clock != 0) + fprintf(stderr, "Warning: clock for C140 specified, but not supported\n"); + + // +AC: K053260 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x161 && clock != 0) + fprintf(stderr, "Warning: clock for K053260 specified, but not supported\n"); + + // +B0: Pokey clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x161 && clock != 0) + fprintf(stderr, "Warning: clock for Pokey specified, but not supported\n"); + + // +B4: QSound clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x161 && clock != 0) + fprintf(stderr, "Warning: clock for QSound specified, but not supported\n"); + + // +B8: SCSP clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x171 && clock != 0) + fprintf(stderr, "Warning: clock for SCSP specified, but not supported\n"); + + // +BC: extra header offset + if (offset + 4 > data_start) + return data_start; + uint32_t extra_header = parse_uint32(buffer, offset); + + // +C0: WonderSwan clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x171 && clock != 0) + fprintf(stderr, "Warning: clock for WonderSwan specified, but not supported\n"); + + // +C4: VSU clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x171 && clock != 0) + fprintf(stderr, "Warning: clock for VSU specified, but not supported\n"); + + // +C8: SAA1099 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x171 && clock != 0) + fprintf(stderr, "Warning: clock for SAA1099 specified, but not supported\n"); + + // +CC: ES5503 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x171 && clock != 0) + fprintf(stderr, "Warning: clock for ES5503 specified, but not supported\n"); + + // +D0: ES5505/ES5506 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x171 && clock != 0) + fprintf(stderr, "Warning: clock for ES5505/ES5506 specified, but not supported\n"); + + // +D4: ES5503 output channels / ES5505/ES5506 amount of output channels / C352 clock divider + if (offset + 4 > data_start) + return data_start; + dummy = parse_uint32(buffer, offset); + + // +D8: X1-010 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x171 && clock != 0) + fprintf(stderr, "Warning: clock for X1-010 specified, but not supported\n"); + + // +DC: C352 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x171 && clock != 0) + fprintf(stderr, "Warning: clock for C352 specified, but not supported\n"); + + // +E0: GA20 clock + if (offset + 4 > data_start) + return data_start; + clock = parse_uint32(buffer, offset); + if (version >= 0x171 && clock != 0) + fprintf(stderr, "Warning: clock for GA20 specified, but not supported\n"); + + return data_start; +} + + +//------------------------------------------------- +// find_chip - find the given chip and index +//------------------------------------------------- + +vgm_chip_base *find_chip(chip_type type, uint8_t index) +{ + for (auto &chip : active_chips) + if (chip->type() == type && index-- == 0) + return chip.get(); + return nullptr; +} + + +//------------------------------------------------- +// write_chip - handle a write to the given chip +// and index +//------------------------------------------------- + +void write_chip(chip_type type, uint8_t index, uint32_t reg, uint8_t data) +{ + vgm_chip_base *chip = find_chip(type, index); + if (chip != nullptr) + chip->write(reg, data); +} + + +//------------------------------------------------- +// add_rom_data - add data to the given chip +// type in the given access class +//------------------------------------------------- + +void add_rom_data(chip_type type, ymfm::access_class access, std::vector<uint8_t> &buffer, uint32_t &localoffset, uint32_t size) +{ + uint32_t length = parse_uint32(buffer, localoffset); + uint32_t start = parse_uint32(buffer, localoffset); + for (int index = 0; index < 2; index++) + { + vgm_chip_base *chip = find_chip(type, index); + if (chip != nullptr) + chip->write_data(access, start, size, &buffer[localoffset]); + } +} + + +//------------------------------------------------- +// generate_all - generate everything described +// in the vgmplay file +//------------------------------------------------- + +void generate_all(std::vector<uint8_t> &buffer, uint32_t data_start, uint32_t output_rate, std::vector<int32_t> &wav_buffer) +{ + // set the offset to the data start and go + uint32_t offset = data_start; + bool done = false; + emulated_time output_step = 0x100000000ull / output_rate; + emulated_time output_pos = 0; + while (!done && offset < buffer.size()) + { + int delay = 0; + uint8_t cmd = buffer[offset++]; + switch (cmd) + { + // YM2413, write value dd to register aa + case 0x51: + case 0xa1: + write_chip(CHIP_YM2413, cmd >> 7, buffer[offset], buffer[offset + 1]); + offset += 2; + break; + + // YM2612 port 0, write value dd to register aa + case 0x52: + case 0xa2: + write_chip(CHIP_YM2612, cmd >> 7, buffer[offset], buffer[offset + 1]); + offset += 2; + break; + + // YM2612 port 1, write value dd to register aa + case 0x53: + case 0xa3: + write_chip(CHIP_YM2612, cmd >> 7, buffer[offset] | 0x100, buffer[offset + 1]); + offset += 2; + break; + + // YM2151, write value dd to register aa + case 0x54: + case 0xa4: + write_chip(CHIP_YM2151, cmd >> 7, buffer[offset], buffer[offset + 1]); + offset += 2; + break; + + // YM2203, write value dd to register aa + case 0x55: + case 0xa5: + write_chip(CHIP_YM2203, cmd >> 7, buffer[offset], buffer[offset + 1]); + offset += 2; + break; + + // YM2608 port 0, write value dd to register aa + case 0x56: + case 0xa6: + write_chip(CHIP_YM2608, cmd >> 7, buffer[offset], buffer[offset + 1]); + offset += 2; + break; + + // YM2608 port 1, write value dd to register aa + case 0x57: + case 0xa7: + write_chip(CHIP_YM2608, cmd >> 7, buffer[offset] | 0x100, buffer[offset + 1]); + offset += 2; + break; + + // YM2610 port 0, write value dd to register aa + case 0x58: + case 0xa8: + write_chip(CHIP_YM2610, cmd >> 7, buffer[offset], buffer[offset + 1]); + offset += 2; + break; + + // YM2610 port 1, write value dd to register aa + case 0x59: + case 0xa9: + write_chip(CHIP_YM2610, cmd >> 7, buffer[offset] | 0x100, buffer[offset + 1]); + offset += 2; + break; + + // YM3812, write value dd to register aa + case 0x5a: + case 0xaa: + write_chip(CHIP_YM3812, cmd >> 7, buffer[offset], buffer[offset + 1]); + offset += 2; + break; + + // YM3526, write value dd to register aa + case 0x5b: + case 0xab: + write_chip(CHIP_YM3526, cmd >> 7, buffer[offset], buffer[offset + 1]); + offset += 2; + break; + + // Y8950, write value dd to register aa + case 0x5c: + case 0xac: + write_chip(CHIP_Y8950, cmd >> 7, buffer[offset], buffer[offset + 1]); + offset += 2; + break; + + // YMF262 port 0, write value dd to register aa + case 0x5e: + case 0xae: + write_chip(CHIP_YMF262, cmd >> 7, buffer[offset], buffer[offset + 1]); + offset += 2; + break; + + // YMF262 port 1, write value dd to register aa + case 0x5f: + case 0xaf: + write_chip(CHIP_YMF262, cmd >> 7, buffer[offset] | 0x100, buffer[offset + 1]); + offset += 2; + break; + + // Wait n samples, n can range from 0 to 65535 (approx 1.49 seconds) + case 0x61: + delay = buffer[offset] | (buffer[offset + 1] << 8); + offset += 2; + break; + + // wait 735 samples (60th of a second) + case 0x62: + delay = 735; + break; + + // wait 882 samples (50th of a second) + case 0x63: + delay = 882; + break; + + // end of sound data + case 0x66: + done = true; + break; + + // data block + case 0x67: + { + uint8_t dummy = buffer[offset++]; + if (dummy != 0x66) + break; + uint8_t type = buffer[offset++]; + uint32_t size = parse_uint32(buffer, offset); + uint32_t localoffset = offset; + + switch (type) + { + case 0x01: // RF5C68 PCM data for use with associated commands + case 0x02: // RF5C164 PCM data for use with associated commands + case 0x03: // PWM PCM data for use with associated commands + case 0x04: // OKIM6258 ADPCM data for use with associated commands + case 0x05: // HuC6280 PCM data for use with associated commands + case 0x06: // SCSP PCM data for use with associated commands + case 0x07: // NES APU DPCM data for use with associated commands + break; + + case 0x00: // YM2612 PCM data for use with associated commands + { + vgm_chip_base *chip = find_chip(CHIP_YM2612, 0); + if (chip != nullptr) + chip->write_data(ymfm::ACCESS_PCM, 0, size - 8, &buffer[localoffset]); + break; + } + + case 0x82: // YM2610 ADPCM ROM data + add_rom_data(CHIP_YM2610, ymfm::ACCESS_ADPCM_A, buffer, localoffset, size - 8); + break; + + case 0x81: // YM2608 DELTA-T ROM data + add_rom_data(CHIP_YM2608, ymfm::ACCESS_ADPCM_B, buffer, localoffset, size - 8); + break; + + case 0x83: // YM2610 DELTA-T ROM data + add_rom_data(CHIP_YM2610, ymfm::ACCESS_ADPCM_B, buffer, localoffset, size - 8); + break; + + case 0x84: // YMF278B ROM data + case 0x87: // YMF278B RAM data + add_rom_data(CHIP_YMF278B, ymfm::ACCESS_PCM, buffer, localoffset, size - 8); + break; + + case 0x88: // Y8950 DELTA-T ROM data + add_rom_data(CHIP_Y8950, ymfm::ACCESS_ADPCM_B, buffer, localoffset, size - 8); + break; + + case 0x80: // Sega PCM ROM data + case 0x85: // YMF271 ROM data + case 0x86: // YMZ280B ROM data + case 0x89: // MultiPCM ROM data + case 0x8A: // uPD7759 ROM data + case 0x8B: // OKIM6295 ROM data + case 0x8C: // K054539 ROM data + case 0x8D: // C140 ROM data + case 0x8E: // K053260 ROM data + case 0x8F: // Q-Sound ROM data + case 0x90: // ES5505/ES5506 ROM data + case 0x91: // X1-010 ROM data + case 0x92: // C352 ROM data + case 0x93: // GA20 ROM data + break; + + case 0xC0: // RF5C68 RAM write + case 0xC1: // RF5C164 RAM write + case 0xC2: // NES APU RAM write + case 0xE0: // SCSP RAM write + case 0xE1: // ES5503 RAM write + break; + + default: + if (type >= 0x40 && type < 0x7f) + printf("Compressed data block not supported\n"); + else + printf("Unknown data block type 0x%02X\n", type); + break; + } + offset += size; + break; + } + + // PCM RAM write + case 0x68: + printf("68: PCM RAM write\n"); + break; + + // AY8910, write value dd to register aa + case 0xa0: + write_chip(CHIP_YM2149, buffer[offset] >> 7, buffer[offset] & 0x7f, buffer[offset + 1]); + offset += 2; + break; + + // pp aa dd: YMF278B, port pp, write value dd to register aa + case 0xd0: + write_chip(CHIP_YMF278B, buffer[offset] >> 7, ((buffer[offset] & 0x7f) << 8) | buffer[offset + 1], buffer[offset + 2]); + offset += 3; + break; + + case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: + case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f: + delay = (cmd & 15) + 1; + break; + + case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: + case 0x88: case 0x89: case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f: + { + vgm_chip_base *chip = find_chip(CHIP_YM2612, 0); + if (chip != nullptr) + chip->write(0x2a, chip->read_pcm()); + delay = cmd & 15; + break; + } + + // ignored, consume one byte + case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: + case 0x38: case 0x39: case 0x3a: case 0x3b: case 0x3c: case 0x3d: case 0x3e: case 0x3f: + case 0x4f: // dd: Game Gear PSG stereo, write dd to port 0x06 + case 0x50: // dd: PSG (SN76489/SN76496) write value dd + offset++; + break; + + // ignored, consume two bytes + case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: + case 0x48: case 0x49: case 0x4a: case 0x4b: case 0x4c: case 0x4d: case 0x4e: + case 0x5d: // aa dd: YMZ280B, write value dd to register aa + case 0xb0: // aa dd: RF5C68, write value dd to register aa + case 0xb1: // aa dd: RF5C164, write value dd to register aa + case 0xb2: // aa dd: PWM, write value ddd to register a (d is MSB, dd is LSB) + case 0xb3: // aa dd: GameBoy DMG, write value dd to register aa + case 0xb4: // aa dd: NES APU, write value dd to register aa + case 0xb5: // aa dd: MultiPCM, write value dd to register aa + case 0xb6: // aa dd: uPD7759, write value dd to register aa + case 0xb7: // aa dd: OKIM6258, write value dd to register aa + case 0xb8: // aa dd: OKIM6295, write value dd to register aa + case 0xb9: // aa dd: HuC6280, write value dd to register aa + case 0xba: // aa dd: K053260, write value dd to register aa + case 0xbb: // aa dd: Pokey, write value dd to register aa + case 0xbc: // aa dd: WonderSwan, write value dd to register aa + case 0xbd: // aa dd: SAA1099, write value dd to register aa + case 0xbe: // aa dd: ES5506, write value dd to register aa + case 0xbf: // aa dd: GA20, write value dd to register aa + offset += 2; + break; + + // ignored, consume three bytes + case 0xc9: case 0xca: case 0xcb: case 0xcc: case 0xcd: case 0xce: case 0xcf: + case 0xd7: case 0xd8: case 0xd9: case 0xda: case 0xdb: case 0xdc: case 0xdd: case 0xde: case 0xdf: + case 0xc0: // bbaa dd: Sega PCM, write value dd to memory offset aabb + case 0xc1: // bbaa dd: RF5C68, write value dd to memory offset aabb + case 0xc2: // bbaa dd: RF5C164, write value dd to memory offset aabb + case 0xc3: // cc bbaa: MultiPCM, write set bank offset aabb to channel cc + case 0xc4: // mmll rr: QSound, write value mmll to register rr (mm - data MSB, ll - data LSB) + case 0xc5: // mmll dd: SCSP, write value dd to memory offset mmll (mm - offset MSB, ll - offset LSB) + case 0xc6: // mmll dd: WonderSwan, write value dd to memory offset mmll (mm - offset MSB, ll - offset LSB) + case 0xc7: // mmll dd: VSU, write value dd to memory offset mmll (mm - offset MSB, ll - offset LSB) + case 0xc8: // mmll dd: X1-010, write value dd to memory offset mmll (mm - offset MSB, ll - offset LSB) + case 0xd1: // pp aa dd: YMF271, port pp, write value dd to register aa + case 0xd2: // pp aa dd: SCC1, port pp, write value dd to register aa + case 0xd3: // pp aa dd: K054539, write value dd to register ppaa + case 0xd4: // pp aa dd: C140, write value dd to register ppaa + case 0xd5: // pp aa dd: ES5503, write value dd to register ppaa + case 0xd6: // pp aa dd: ES5506, write value aadd to register pp + offset += 3; + break; + + // ignored, consume four bytes + case 0xe0: // dddddddd: Seek to offset dddddddd (Intel byte order) in PCM data bank of data block type 0 (YM2612). + { + vgm_chip_base *chip = find_chip(CHIP_YM2612, 0); + uint32_t pos = parse_uint32(buffer, offset); + if (chip != nullptr) + chip->seek_pcm(pos); + offset += 4; + break; + } + case 0xe1: // mmll aadd: C352, write value aadd to register mmll + case 0xe2: case 0xe3: case 0xe4: case 0xe5: case 0xe6: case 0xe7: + case 0xe8: case 0xe9: case 0xea: case 0xeb: case 0xec: case 0xed: case 0xee: case 0xef: + case 0xf0: case 0xf1: case 0xf2: case 0xf3: case 0xf4: case 0xf5: case 0xf6: case 0xf7: + case 0xf8: case 0xf9: case 0xfa: case 0xfb: case 0xfc: case 0xfd: case 0xfe: case 0xff: + offset += 4; + break; + } + + // handle delays + while (delay-- != 0) + { + bool more_remaining = false; + int32_t outputs[2] = { 0 }; + for (auto &chip : active_chips) + chip->generate(output_pos, output_step, outputs); + output_pos += output_step; + wav_buffer.push_back(outputs[0]); + wav_buffer.push_back(outputs[1]); + } + } +} + + +//------------------------------------------------- +// write_wav - write a WAV file from the provided +// stereo data +//------------------------------------------------- + +int write_wav(char const *filename, uint32_t output_rate, std::vector<int32_t> &wav_buffer_src) +{ + // determine normalization parameters + int32_t max_scale = 0; + for (size_t index = 0; index < wav_buffer_src.size(); index++) + { + int32_t absval = std::abs(wav_buffer_src[index]); + max_scale = std::max(max_scale, absval); + } + + // warn if only silence was detected (and also avoid divide by zero) + if (max_scale == 0) + { + fprintf(stderr, "The WAV file data will only contain silence.\n"); + max_scale = 1; + } + + // now convert + std::vector<int16_t> wav_buffer(wav_buffer_src.size()); + for (size_t index = 0; index < wav_buffer_src.size(); index++) + wav_buffer[index] = wav_buffer_src[index] * 26000 / max_scale; + + // write the WAV file + FILE *out = fopen(filename, "wb"); + if (out == nullptr) + { + fprintf(stderr, "Error creating output file '%s'\n", filename); + return 6; + } + + // write the 'RIFF' header + if (fwrite("RIFF", 1, 4, out) != 4) + { + fprintf(stderr, "Error writing to output file\n"); + return 7; + } + + // write the total size + uint32_t total_size = 48 + wav_buffer.size() * 2 - 8; + uint8_t wavdata[4]; + wavdata[0] = total_size >> 0; + wavdata[1] = total_size >> 8; + wavdata[2] = total_size >> 16; + wavdata[3] = total_size >> 24; + if (fwrite(wavdata, 1, 4, out) != 4) + { + fprintf(stderr, "Error writing to output file\n"); + return 7; + } + + // write the 'WAVE' type + if (fwrite("WAVE", 1, 4, out) != 4) + { + fprintf(stderr, "Error writing to output file\n"); + return 7; + } + + // write the 'fmt ' tag + if (fwrite("fmt ", 1, 4, out) != 4) + { + fprintf(stderr, "Error writing to output file\n"); + return 7; + } + + // write the format length + wavdata[0] = 16; + wavdata[1] = 0; + wavdata[2] = 0; + wavdata[3] = 0; + if (fwrite(wavdata, 1, 4, out) != 4) + { + fprintf(stderr, "Error writing to output file\n"); + return 7; + } + + // write the format (PCM) + wavdata[0] = 1; + wavdata[1] = 0; + if (fwrite(wavdata, 1, 2, out) != 2) + { + fprintf(stderr, "Error writing to output file\n"); + return 7; + } + + // write the channels + wavdata[0] = 2; + wavdata[1] = 0; + if (fwrite(wavdata, 1, 2, out) != 2) + { + fprintf(stderr, "Error writing to output file\n"); + return 7; + } + + // write the sample rate + wavdata[0] = output_rate >> 0; + wavdata[1] = output_rate >> 8; + wavdata[2] = output_rate >> 16; + wavdata[3] = output_rate >> 24; + if (fwrite(wavdata, 1, 4, out) != 4) + { + fprintf(stderr, "Error writing to output file\n"); + return 7; + } + + // write the bytes/second + uint32_t bps = output_rate * 2 * 2; + wavdata[0] = bps >> 0; + wavdata[1] = bps >> 8; + wavdata[2] = bps >> 16; + wavdata[3] = bps >> 24; + if (fwrite(wavdata, 1, 4, out) != 4) + { + fprintf(stderr, "Error writing to output file\n"); + return 7; + } + + // write the block align + wavdata[0] = 4; + wavdata[1] = 0; + if (fwrite(wavdata, 1, 2, out) != 2) + { + fprintf(stderr, "Error writing to output file\n"); + return 7; + } + + // write the bits/sample + wavdata[0] = 16; + wavdata[1] = 0; + if (fwrite(wavdata, 1, 2, out) != 2) + { + fprintf(stderr, "Error writing to output file\n"); + return 7; + } + + // write the 'data' tag + if (fwrite("data", 1, 4, out) != 4) + { + fprintf(stderr, "Error writing to output file\n"); + return 7; + } + + // write the data length + uint32_t datalen = wav_buffer.size() * 2; + wavdata[0] = datalen >> 0; + wavdata[1] = datalen >> 8; + wavdata[2] = datalen >> 16; + wavdata[3] = datalen >> 24; + if (fwrite(wavdata, 1, 4, out) != 4) + { + fprintf(stderr, "Error writing to output file\n"); + return 7; + } + + // write the data + if (fwrite(&wav_buffer[0], 1, datalen, out) != datalen) + { + fprintf(stderr, "Error writing to output file\n"); + return 7; + } + fclose(out); + return 0; +} + + +//------------------------------------------------- +// main - program entry point +//------------------------------------------------- + +int main(int argc, char *argv[]) +{ + char const *filename = nullptr; + char const *outfilename = nullptr; + int output_rate = 44100; + + // parse command line + bool argerr = false; + for (int arg = 1; arg < argc; arg++) + { + char const *curarg = argv[arg]; + if (*curarg == '-') + { + if (strcmp(curarg, "-o") == 0 || strcmp(curarg, "--output") == 0) + outfilename = argv[++arg]; + else if (strcmp(curarg, "-r") == 0 || strcmp(curarg, "--samplerate") == 0) + output_rate = atoi(argv[++arg]); + else + { + fprintf(stderr, "Unknown argument: %s\n", curarg); + argerr = true; + } + } + else + filename = curarg; + } + + // if invalid syntax, show usage + if (argerr || filename == nullptr || outfilename == nullptr) + { + fprintf(stderr, "Usage: vgmrender <inputfile> -o <outputfile> [-r <rate>]\n"); + return 1; + } + + // attempt to read the file + FILE *file = fopen(filename, "rb"); + if (file == nullptr) + { + fprintf(stderr, "Error opening file '%s'\n", filename); + return 2; + } + + // get the length and create a buffer + fseek(file, 0, SEEK_END); + uint32_t size = ftell(file); + fseek(file, 0, SEEK_SET); + std::vector<uint8_t> buffer(size); + + // read the contents + auto bytes_read = fread(&buffer[0], 1, size, file); + if (bytes_read != size) + { + fprintf(stderr, "Error reading file contents\n"); + return 3; + } + fclose(file); + + // check for gzip-format + if (buffer.size() >= 10 && buffer[0] == 0x1f && buffer[1] == 0x8b && buffer[2] == 0x08) + { + // copy the raw data to a new buffer + std::vector<uint8_t> compressed = buffer; + + // determine uncompressed size and resize the buffer + uint8_t *end = &compressed[compressed.size()]; + uint32_t uncompressed = end[-4] | (end[-3] << 8) | (end[-2] << 16) | (end[-1] << 24); + if (size < compressed.size() || size > 32*1024*1024) + { + fprintf(stderr, "File '%s' appears to be a compressed file but has unexpected size of %d\n", filename, size); + return 4; + } + buffer.resize(uncompressed); + + // decompress the data + auto result = em_inflate(&compressed[0], compressed.size(), &buffer[0], buffer.size()); + if (result == -1) + { + fprintf(stderr, "Error decompressing data from file\n"); + return 4; + } + } + + // check the ID + uint32_t offset = 0; + if (buffer.size() < 64 || buffer[0] != 'V' || buffer[1] != 'g' || buffer[2] != 'm' || buffer[3] != ' ') + { + fprintf(stderr, "File '%s' does not appear to be a valid VGM file\n", filename); + return 4; + } + + // parse the header, creating any chips needed + uint32_t data_start = parse_header(buffer); + + // if no chips created, fail + if (active_chips.size() == 0) + { + fprintf(stderr, "No compatible chips found, exiting.\n"); + return 5; + } + + // generate the output + std::vector<int32_t> wav_buffer; + generate_all(buffer, data_start, output_rate, wav_buffer); + + int err = write_wav(outfilename, output_rate, wav_buffer); + +#if (CAPTURE_NATIVE) + { + int chipnum = 0; + for (auto &chip : active_chips) + if (err == 0 && chip->m_native_data.size() > 0) + { + char filename[20]; + sprintf(filename, "native-%d.wav", chipnum++); + err = write_wav(filename, chip->sample_rate(), chip->m_native_data); + } + } +#endif +#if (RUN_NUKED_OPN2) + { + int chipnum = 0; + for (auto &chip : active_chips) + if (err == 0 && chip->m_nuked_data.size() > 0) + { + char filename[20]; + sprintf(filename, "nuked-%d.wav", chipnum++); + err = write_wav(filename, chip->sample_rate(), chip->m_nuked_data); + } + } +#endif + + active_chips.clear(); + + return err; +} + +#if (RUN_NUKED_OPN2) +namespace nuked { +#include "test/ym3438.c" +} +#endif |