diff --git a/.gitignore b/.gitignore index 85ada1b..e77be81 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -.vscode/ -builddir/ +.vscode +builddir +_gitignore diff --git a/.gitmodules b/.gitmodules index e02f997..11c7cf8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "contrib/TDA8425_emu"] path = contrib/TDA8425_emu url = https://github.com/TexZK/TDA8425_emu.git +[submodule "contrib/adplug"] + path = contrib/adplug + url = https://github.com/adplug/adplug.git diff --git a/apps/aymo_ymf262_play.c b/apps/aymo_ymf262_play.c index 1f231f3..02095cc 100644 --- a/apps/aymo_ymf262_play.c +++ b/apps/aymo_ymf262_play.c @@ -35,6 +35,8 @@ To play via shell pipe, run: #include "aymo_score_avd.h" #include "aymo_score_dro.h" #include "aymo_score_imf.h" +#include "aymo_score_raw.h" +#include "aymo_score_vgm.h" #include "aymo_wave.h" #include "aymo_ymf262.h" @@ -97,6 +99,8 @@ static union app_scores { struct aymo_score_avd_instance avd; struct aymo_score_dro_instance dro; struct aymo_score_imf_instance imf; + struct aymo_score_raw_instance raw; + struct aymo_score_vgm_instance vgm; } score; static struct aymo_ymf262_chip* chip; diff --git a/contrib/adplug b/contrib/adplug new file mode 160000 index 0000000..7db72f7 --- /dev/null +++ b/contrib/adplug @@ -0,0 +1 @@ +Subproject commit 7db72f7e47d8c576b7af7c937d036add8d0c98d4 diff --git a/include/aymo_score.h b/include/aymo_score.h index f51567e..b685f6d 100644 --- a/include/aymo_score.h +++ b/include/aymo_score.h @@ -33,6 +33,7 @@ enum aymo_score_type { aymo_score_type_dro, aymo_score_type_imf, aymo_score_type_raw, + aymo_score_type_vgm, aymo_score_type_unknown }; diff --git a/include/aymo_score_vgm.h b/include/aymo_score_vgm.h new file mode 100644 index 0000000..e3d522f --- /dev/null +++ b/include/aymo_score_vgm.h @@ -0,0 +1,181 @@ +/* +AYMO - Accelerated YaMaha Operator +Copyright (c) 2023 Andrea Zoppi. + +This file is part of AYMO. + +AYMO is free software: you can redistribute it and/or modify it under the +terms of the GNU Lesser General Public License as published by the Free +Software Foundation, either version 2.1 of the License, or (at your option) +any later version. + +AYMO is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. + +You should have received a copy of the GNU Lesser General Public License +along with AYMO. If not, see . +*/ +#ifndef _include_aymo_score_vgm_h +#define _include_aymo_score_vgm_h + +#include "aymo_score.h" + +AYMO_CXX_EXTERN_C_BEGIN + + +// See: https://vgmrips.net/wiki/VGM_Specification +enum aymo_score_vgm_offset { + aymo_score_vgm_offset_vgm_ident = 0x00, + aymo_score_vgm_offset_eof_offset = 0x04, + aymo_score_vgm_offset_version = 0x08, + aymo_score_vgm_offset_sn76489_clock = 0x0C, + + aymo_score_vgm_offset_ym2413_clock = 0x10, + aymo_score_vgm_offset_gd3_offset = 0x14, + aymo_score_vgm_offset_total_samples = 0x18, + aymo_score_vgm_offset_loop_offset = 0x1C, + + aymo_score_vgm_offset_loop_samples = 0x20, + aymo_score_vgm_offset_rate = 0x24, + aymo_score_vgm_offset_sn76489_feedback = 0x28, + aymo_score_vgm_offset_sn76489_width = 0x2A, + aymo_score_vgm_offset_sn76489_flags = 0x2B, + aymo_score_vgm_offset_ym2612_clock = 0x2C, + + aymo_score_vgm_offset_ym2151_clock = 0x30, + aymo_score_vgm_offset_vgm_data_offset = 0x34, + aymo_score_vgm_offset_sega_pcm_clock = 0x38, + aymo_score_vgm_offset_spcm_interface = 0x3C, + + aymo_score_vgm_offset_rf5c68_clock = 0x40, + aymo_score_vgm_offset_ym2203_clock = 0x44, + aymo_score_vgm_offset_ym2608_clock = 0x48, + aymo_score_vgm_offset_ym2610b_clock = 0x4C, + + aymo_score_vgm_offset_ym3812_clock = 0x50, + aymo_score_vgm_offset_ym3526_clock = 0x54, + aymo_score_vgm_offset_y8950_clock = 0x58, + aymo_score_vgm_offset_ymf262_clock = 0x5C, + + aymo_score_vgm_offset_ymf278b_clock = 0x60, + aymo_score_vgm_offset_ymf271_clock = 0x64, + aymo_score_vgm_offset_ymz280b_clock = 0x68, + aymo_score_vgm_offset_rf5c164_clock = 0x6C, + + aymo_score_vgm_offset_pwm_clock = 0x70, + aymo_score_vgm_offset_ay8910_clock = 0x74, + aymo_score_vgm_offset_ay8910_chip_type = 0x78, + aymo_score_vgm_offset_ay8910_flags = 0x79, + aymo_score_vgm_offset_volume_modifier = 0x7C, + aymo_score_vgm_offset_reserved_0dh = 0x7D, + aymo_score_vgm_offset_loop_base = 0x7E, + aymo_score_vgm_offset_loop_modifier = 0x7F, + + aymo_score_vgm_offset_gb_dmg_clock = 0x80, + aymo_score_vgm_offset_nes_apu_clock = 0x84, + aymo_score_vgm_offset_multipcm_clock = 0x88, + aymo_score_vgm_offset_upd7759_clock = 0x8C, + + aymo_score_vgm_offset_okim6258_clock = 0x90, + aymo_score_vgm_offset_okim6258_flags = 0x94, + aymo_score_vgm_offset_k054539_flags = 0x95, + aymo_score_vgm_offset_c140_chip_type = 0x96, + aymo_score_vgm_offset_reserved_97h = 0x97, + aymo_score_vgm_offset_okim6295_clock = 0x98, + aymo_score_vgm_offset_k051649_clock = 0x9C, + + aymo_score_vgm_offset_k054539_clock = 0xA0, + aymo_score_vgm_offset_huc6280_clock = 0xA4, + aymo_score_vgm_offset_c140_clock = 0xa8, + aymo_score_vgm_offset_k053260_clock = 0xAC, + + aymo_score_vgm_offset_pokey_clock = 0xB0, + aymo_score_vgm_offset_qsound_clock = 0xB4, + aymo_score_vgm_offset_scsp_clock = 0xB8, + aymo_score_vgm_offset_extra_header_offset = 0xBC, + + aymo_score_vgm_offset_wonderswan_clock = 0xC0, + aymo_score_vgm_offset_vsu_clock = 0xC4, + aymo_score_vgm_offset_saa1099_clock = 0xC8, + aymo_score_vgm_offset_es5503_clock = 0xCC, + + aymo_score_vgm_offset_es5506_clock = 0xD0, + aymo_score_vgm_offset_es_chns = 0xD4, + aymo_score_vgm_offset_c352_clock_divider = 0xD6, + aymo_score_vgm_offset_reserved_d7h = 0xD7, + aymo_score_vgm_offset_x1_010_clock = 0xD8, + aymo_score_vgm_offset_c352_clock = 0xDC, + + aymo_score_vgm_offset_ga20_clock = 0xE0, + aymo_score_vgm_offset_reserved_e4h = 0xE4, + aymo_score_vgm_offset_reserved_e8h = 0xE8, + aymo_score_vgm_offset_reserved_ech = 0xEC, + + aymo_score_vgm_offset_reserved_f0h = 0xF0, + aymo_score_vgm_offset_reserved_f4h = 0xF4, + aymo_score_vgm_offset_reserved_f8h = 0xF8, + aymo_score_vgm_offset_reserved_fch = 0xFC +}; + +enum aymo_score_vgm_type { + aymo_score_vgm_type_opl1, + aymo_score_vgm_type_opl2, + aymo_score_vgm_type_opl3 +}; + + +struct aymo_score_vgm_instance { + const struct aymo_score_vt* vt; + struct aymo_score_status status; + const uint8_t* events; + uint32_t opl_rate; + uint32_t division; + uint32_t eof_offset; + uint32_t total_samples; + uint32_t loop_samples; + uint32_t loop_offset; + uint32_t offset; + uint32_t index; +}; + + +AYMO_PUBLIC const struct aymo_score_vt aymo_score_vgm_vt; + + +AYMO_PUBLIC int aymo_score_vgm_ctor( + struct aymo_score_vgm_instance* score +); + +AYMO_PUBLIC void aymo_score_vgm_dtor( + struct aymo_score_vgm_instance* score +); + +AYMO_PUBLIC int aymo_score_vgm_load( + struct aymo_score_vgm_instance* score, + const void* data, + uint32_t size +); + +AYMO_PUBLIC void aymo_score_vgm_unload( + struct aymo_score_vgm_instance* score +); + +AYMO_PUBLIC struct aymo_score_status* aymo_score_vgm_get_status( + struct aymo_score_vgm_instance* score +); + +AYMO_PUBLIC void aymo_score_vgm_restart( + struct aymo_score_vgm_instance* score +); + +AYMO_PUBLIC uint32_t aymo_score_vgm_tick( + struct aymo_score_vgm_instance* score, + uint32_t count +); + + +AYMO_CXX_EXTERN_C_END + +#endif // _include_aymo_score_vgm_h diff --git a/meson.build b/meson.build index b5d6244..c7bd271 100644 --- a/meson.build +++ b/meson.build @@ -443,6 +443,7 @@ sources = { 'include/aymo_score_dro.h', 'include/aymo_score_imf.h', 'include/aymo_score_raw.h', + 'include/aymo_score_vgm.h', 'include/aymo_wave.h', 'include/aymo_ymf262_arm_neon.h', 'include/aymo_ymf262_none.h', @@ -461,6 +462,7 @@ sources = { 'src/aymo_score_dro.c', 'src/aymo_score_imf.c', 'src/aymo_score_raw.c', + 'src/aymo_score_vgm.c', 'src/aymo_tda8425.c', 'src/aymo_tda8425_common.c', 'src/aymo_tda8425_none.c', diff --git a/src/aymo_score.c b/src/aymo_score.c index 019a3fb..0427a18 100644 --- a/src/aymo_score.c +++ b/src/aymo_score.c @@ -23,6 +23,7 @@ along with AYMO. If not, see . #include "aymo_score_dro.h" #include "aymo_score_imf.h" #include "aymo_score_raw.h" +#include "aymo_score_vgm.h" #include @@ -131,6 +132,12 @@ enum aymo_score_type aymo_score_ext_to_type( (tag[3] == '\0')) { return aymo_score_type_raw; } + if (((tag[0] == 'V') || (tag[0] == 'v')) && + ((tag[1] == 'G') || (tag[1] == 'g')) && + ((tag[2] == 'M') || (tag[2] == 'm')) && + (tag[3] == '\0')) { + return aymo_score_type_vgm; + } } return aymo_score_type_unknown; } @@ -145,6 +152,7 @@ const struct aymo_score_vt* aymo_score_type_to_vt( case aymo_score_type_dro: return &aymo_score_dro_vt; case aymo_score_type_imf: return &aymo_score_imf_vt; case aymo_score_type_raw: return &aymo_score_raw_vt; + case aymo_score_type_vgm: return &aymo_score_vgm_vt; default: return NULL; } } diff --git a/src/aymo_score_dro.c b/src/aymo_score_dro.c index e9ab2db..6588b62 100644 --- a/src/aymo_score_dro.c +++ b/src/aymo_score_dro.c @@ -131,12 +131,14 @@ int aymo_score_dro_load( if ((((header->version_major == 0u) && (header->version_minor == 1u)) || ((header->version_major == 1u) && (header->version_minor == 0u)))) { + if (size < sizeof(struct aymo_score_dro_v1_header)) { return 1; } v1_header = (const struct aymo_score_dro_v1_header*)(const void*)ptr; ptr += sizeof(struct aymo_score_dro_v1_header); size -= sizeof(struct aymo_score_dro_v1_header); + if ((v1_header->hardware_extra[0] || v1_header->hardware_extra[1] || v1_header->hardware_extra[2])) { diff --git a/src/aymo_score_vgm.c b/src/aymo_score_vgm.c new file mode 100644 index 0000000..64dae21 --- /dev/null +++ b/src/aymo_score_vgm.c @@ -0,0 +1,312 @@ +/* +AYMO - Accelerated YaMaha Operator +Copyright (c) 2023 Andrea Zoppi. + +This file is part of AYMO. + +AYMO is free software: you can redistribute it and/or modify it under the +terms of the GNU Lesser General Public License as published by the Free +Software Foundation, either version 2.1 of the License, or (at your option) +any later version. + +AYMO is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. + +You should have received a copy of the GNU Lesser General Public License +along with AYMO. If not, see . +*/ + +#include "aymo_score_vgm.h" + +#include + +AYMO_CXX_EXTERN_C_BEGIN + + +const struct aymo_score_vt aymo_score_vgm_vt = { + "aymo_score_vgm", + (aymo_score_ctor_f)aymo_score_vgm_ctor, + (aymo_score_dtor_f)aymo_score_vgm_dtor, + (aymo_score_load_f)aymo_score_vgm_load, + (aymo_score_unload_f)aymo_score_vgm_unload, + (aymo_score_get_status_f)aymo_score_vgm_get_status, + (aymo_score_restart_f)aymo_score_vgm_restart, + (aymo_score_tick_f)aymo_score_vgm_tick +}; + + +static inline uint16_t decode_u16le(const uint8_t* ptr) +{ + uint16_t value = ( + ((uint16_t)ptr[0] << 0u) | + ((uint16_t)ptr[1] << 8u) + ); + return value; +} + + +static inline uint32_t decode_u32le(const uint8_t* ptr) +{ + uint32_t value = ( + ((uint32_t)ptr[0] << 0u) | + ((uint32_t)ptr[1] << 8u) | + ((uint32_t)ptr[2] << 16u) | + ((uint32_t)ptr[3] << 24u) + ); + return value; +} + + +int aymo_score_vgm_ctor( + struct aymo_score_vgm_instance* score +) +{ + assert(score); + + uint32_t opl_rate = AYMO_SCORE_OPL_RATE_DEFAULT; + uint32_t division = (opl_rate / 1000u); // TODO: improve resolution via fixed point 24.8 + division += (uint32_t)(division == 0u); + + score->vt = &aymo_score_vgm_vt; + + score->events = NULL; + + score->opl_rate = opl_rate; + score->division = division; + score->eof_offset = 0u; + score->total_samples = 0u; + score->loop_samples = 0u; + score->loop_offset = 0u; + + aymo_score_vgm_restart(score); + return 0; +} + + +void aymo_score_vgm_dtor( + struct aymo_score_vgm_instance* score +) +{ + AYMO_UNUSED_VAR(score); + assert(score); +} + + +int aymo_score_vgm_load( + struct aymo_score_vgm_instance* score, + const void* data, + uint32_t size +) +{ + assert(score); + assert(data); + assert(size); + + if (size < 0x40u) { + return 1; + } + const uint8_t* ptr = data; + uint32_t ident = decode_u32le(&ptr[aymo_score_vgm_offset_vgm_ident]); + if (ident != 0x206D6756uL) { // "Vgm " + return 1; + } + uint32_t version = decode_u32le(&ptr[aymo_score_vgm_offset_version]); + if (version < 0x151uL) { + return 1; + } + uint32_t eof_offset = decode_u32le(&ptr[aymo_score_vgm_offset_eof_offset]); + eof_offset += (uint32_t)aymo_score_vgm_offset_eof_offset; + if (size < eof_offset) { + return 1; + } + uint32_t data_offset = decode_u32le(&ptr[aymo_score_vgm_offset_vgm_data_offset]); + data_offset += (uint32_t)aymo_score_vgm_offset_vgm_data_offset; + if (size < data_offset) { + return 1; + } + uint32_t total_samples = decode_u32le(&ptr[aymo_score_vgm_offset_total_samples]); + uint32_t loop_samples = decode_u32le(&ptr[aymo_score_vgm_offset_loop_samples]); + uint32_t loop_offset = decode_u32le(&ptr[aymo_score_vgm_offset_loop_offset]); + loop_offset += (uint32_t)aymo_score_vgm_offset_loop_offset; + + if (loop_samples > total_samples) { + loop_samples = 0u; + } + if (loop_offset > eof_offset) { + loop_offset = 0u; + } + if (!loop_offset || !loop_samples) { + loop_samples = 0u; + loop_offset = 0u; + } + + score->events = &ptr[data_offset]; + + score->eof_offset = (eof_offset - data_offset); + score->total_samples = total_samples; + score->loop_samples = loop_samples; + score->loop_offset = (loop_offset - data_offset); + + aymo_score_vgm_restart(score); + return 0; +} + + +void aymo_score_vgm_unload( + struct aymo_score_vgm_instance* score +) +{ + aymo_score_vgm_restart(score); +} + + +struct aymo_score_status* aymo_score_vgm_get_status( + struct aymo_score_vgm_instance* score +) +{ + assert(score); + return &score->status; +} + + +void aymo_score_vgm_restart( + struct aymo_score_vgm_instance* score +) +{ + assert(score); + + score->offset = 0u; + score->index = 0u; + + score->status.delay = 0u; + score->status.address = 0u; + score->status.value = 0u; + score->status.flags = 0u; + + if ((score->offset >= score->eof_offset) || + (score->index >= score->total_samples)) { + + score->status.flags |= AYMO_SCORE_FLAG_EOF; + } +} + + +static void aymo_score_vgm_decode( + struct aymo_score_vgm_instance* score +) +{ + const uint8_t* ptr = &(score->events[score->offset]); + uint8_t opcode = ptr[0]; + uint32_t skip = 1u; + uint32_t wait = 0u; + + if ((opcode >= 0x5Au) && (opcode <= 0x5Fu)) { + score->status.address = ptr[1]; + score->status.value = ptr[2]; + score->status.flags = AYMO_SCORE_FLAG_EVENT; + skip = 3u; + + if (opcode == 0x5Fu) { + score->status.address |= 0x100u; + } + } + else if (opcode == 0x61u) { + wait = decode_u16le(&ptr[1]); + skip = 3u; + } + else if (opcode == 0x62u) { + wait = 735u; + } + else if (opcode == 0x63u) { + wait = 882u; + } + else if (opcode == 0x66u) { + score->status.flags |= AYMO_SCORE_FLAG_EOF; + } + else if ((opcode >= 0x70u) && (opcode <= 0x7Fu)) { + wait = (opcode - 0x70u + 1u); + } + else if ((opcode >= 0x80u) && (opcode <= 0x8Fu)) { + wait = (opcode - 0x80u); + } + else if ((opcode >= 0x30u) && (opcode <= 0x3Fu)) { + skip = 2u; + } + else if ((opcode >= 0x40u) && (opcode <= 0x4Eu)) { + skip = 3u; + } + else if ((opcode >= 0x4Fu) && (opcode <= 0x50u)) { + skip = 2u; + } + else if ((opcode >= 0x51u) && (opcode <= 0x61u)) { + skip = 3u; + } + else if ((opcode >= 0xA0u) && (opcode <= 0xBFu)) { + skip = 3u; + } + else if ((opcode >= 0xC0u) && (opcode <= 0xDFu)) { + skip = 4u; + } + else if (opcode >= 0xE0u) { + skip = 5u; + } + score->offset += skip; + + if (wait) { + uint32_t delay = ((wait * AYMO_SCORE_OPL_RATE_DEFAULT) / 44100u); + score->status.delay = delay; + score->status.flags |= AYMO_SCORE_FLAG_DELAY; + } +} + + +uint32_t aymo_score_vgm_tick( + struct aymo_score_vgm_instance* score, + uint32_t count +) +{ + assert(score); + assert(!score->eof_offset || score->events); + + uint32_t pending = count; + + do { + if (pending >= score->status.delay) { + pending -= score->status.delay; + score->status.delay = 0u; + } + else { + score->status.delay -= pending; + pending = 0u; + } + + score->status.address = 0u; + score->status.value = 0u; + score->status.flags = 0u; + + if (score->status.delay) { + score->status.flags = AYMO_SCORE_FLAG_DELAY; + } + else if ((score->offset < score->eof_offset) && + (score->index < score->total_samples)) { + + aymo_score_vgm_decode(score); + + if (score->status.flags & AYMO_SCORE_FLAG_EVENT) { + count -= pending; // FIXME: what if another event follows immediately? --> count -= CONSUMED + break; + } + } + else { // TODO: support for loops + score->status.flags = AYMO_SCORE_FLAG_EOF; + break; + } + } while (pending); + + return count; +} + + +AYMO_CXX_EXTERN_C_END diff --git a/tests/meson.build b/tests/meson.build index 6551089..5baf453 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -217,7 +217,7 @@ foreach intr_name : ['none', 'x86_sse41', 'x86_avx2', 'arm_neon'] test_suite = 'test_tda8425_@0@_sweep'.format(intr_name) test_exe = get_variable('@0@_exe'.format(test_suite)) foreach test_name, test_args : aymo_tda8425_sweep_suite - test_name = ('_'.join([test_suite] + [test_name])).underscorify() + test_name = ('_'.join([test_suite, test_name])).underscorify() test(test_name, test_exe, args: test_args) endforeach endif @@ -371,7 +371,7 @@ foreach intr_name : ['none', 'x86_sse41', 'arm_neon'] test_suite = 'test_ym7128_@0@_sweep'.format(intr_name) test_exe = get_variable('@0@_exe'.format(test_suite)) foreach test_name, test_args : aymo_ym7128_sweep_suite - test_name = ('_'.join([test_suite] + [test_name])).underscorify() + test_name = ('_'.join([test_suite, test_name])).underscorify() test(test_name, test_exe, args: test_args) endforeach endif @@ -381,12 +381,22 @@ endforeach # ===================================================================== # YMF262 +# [[format, path], ...] +aymo_ymf262_compare_suite = [ + ['dro', '../contrib/adplug/test/testmus/samurai.dro'], +] + foreach intr_name : ['none', 'x86_sse41', 'x86_avx2', 'arm_neon'] have_intr = get_variable('aymo_have_@0@'.format(intr_name)) if have_intr # TODO: improve testing scores - test_name = 'test_ymf262_@0@_compare'.format(intr_name) - test_exe = get_variable('@0@_exe'.format(test_name)) - test(test_name, test_exe, args: ['avd', '../tests/scores/DUNE.avd']) + test_suite = 'test_ymf262_@0@_compare'.format(intr_name) + test_exe = get_variable('@0@_exe'.format(test_suite)) + foreach test_args : aymo_ymf262_compare_suite + score_type = test_args[0] + score_path = test_args[1] + test_name = ('_'.join([test_suite, fs.name(score_path)])).underscorify() + test(test_name, test_exe, args: test_args) + endforeach endif endforeach diff --git a/tests/test_ymf262_compare_prologue_inline.h b/tests/test_ymf262_compare_prologue_inline.h index 6828031..e6b4450 100644 --- a/tests/test_ymf262_compare_prologue_inline.h +++ b/tests/test_ymf262_compare_prologue_inline.h @@ -22,6 +22,8 @@ along with AYMO. If not, see . #include "aymo_score_dro.h" #include "aymo_score_avd.h" #include "aymo_score_imf.h" +#include "aymo_score_raw.h" +#include "aymo_score_vgm.h" #include "aymo_testing.h" #include "aymo_ymf262.h" @@ -61,6 +63,8 @@ static union app_scores { struct aymo_score_avd_instance avd; struct aymo_score_dro_instance dro; struct aymo_score_imf_instance imf; + struct aymo_score_raw_instance raw; + struct aymo_score_vgm_instance vgm; } score; static struct aymo_(chip) aymo_chip;