diff --git a/.ci/AppImageBuilder.yml b/.ci/AppImageBuilder.yml index 64f8f91ed7..7f312baaef 100644 --- a/.ci/AppImageBuilder.yml +++ b/.ci/AppImageBuilder.yml @@ -72,6 +72,7 @@ AppDir: - libxkbcommon-x11-0 # if QT:BOOL=ON - qtwayland5 # if QT:BOOL=ON - zlib1g + - libserialport0 files: exclude: - etc diff --git a/.ci/build.sh b/.ci/build.sh index bf23f5d38a..3269521861 100755 --- a/.ci/build.sh +++ b/.ci/build.sh @@ -525,6 +525,9 @@ then cmake_flags_extra="$cmake_flags_extra -D MOLTENVK=ON -D \"MOLTENVK_INCLUDE_DIR=$macports\"" fi + # Enable Libserialport + cmake_flags_extra="$cmake_flags_extra -D \"LIBSERIALPORT_ROOT=$macports\"" + # Install dependencies only if we're in a new build and/or MacPorts prefix. if check_buildtag "$(basename "$macports")" then @@ -539,11 +542,11 @@ then sudo sed -i -e 's/configure.env-append MAKE=/configure.env-append VULKAN_SDK=${prefix} MAKE=/g' "$qt5_portfile" fi - # Patch openal-soft to use 1.23.1 on all targets instead of 1.24.0 on >=11.0 only, + # Patch openal-soft to use 1.23.1 on all targets instead of 1.24.1 on >=10.15 only, # to prevent a symlink mismatch from having different versions on x86_64 and arm64. # See: https://github.com/macports/macports-ports/commit/9b4903fc9c76769d476079e404c9a3b8a225f8aa openal_portfile="$macports/var/macports/sources/rsync.macports.org/macports/release/tarballs/ports/audio/openal-soft/Portfile" - sudo sed -i -e 's/if {${os.platform} ne "darwin" || ${os.major} >= 21}/if {0}/g' "$openal_portfile" + sudo sed -i -e 's/if {${os.platform} ne "darwin" || ${os.major} >= 19}/if {0}/g' "$openal_portfile" # Patch wget to remove libproxy support, as it depends on shared-mime-info which # fails to build for a 10.13 target, which we have to do despite wget only being @@ -622,7 +625,7 @@ else # ...and the ones we do want listed. Non-dev packages fill missing spots on the list. libpkgs="" longest_libpkg=0 - for pkg in libc6-dev libstdc++6 libopenal-dev libfreetype6-dev libx11-dev libsdl2-dev libpng-dev librtmidi-dev qtdeclarative5-dev libwayland-dev libevdev-dev libxkbcommon-x11-dev libglib2.0-dev libslirp-dev libfaudio-dev libaudio-dev libjack-jackd2-dev libpipewire-0.3-dev libsamplerate0-dev libsndio-dev libvdeplug-dev libfluidsynth-dev libsndfile1-dev + for pkg in libc6-dev libstdc++6 libopenal-dev libfreetype6-dev libx11-dev libsdl2-dev libpng-dev librtmidi-dev qtdeclarative5-dev libwayland-dev libevdev-dev libxkbcommon-x11-dev libglib2.0-dev libslirp-dev libfaudio-dev libaudio-dev libjack-jackd2-dev libpipewire-0.3-dev libsamplerate0-dev libsndio-dev libvdeplug-dev libfluidsynth-dev libsndfile1-dev libserialport-dev do libpkgs="$libpkgs $pkg:$arch_deb" length=$(echo -n $pkg | sed 's/-dev$//' | sed "s/qtdeclarative/qt/" | wc -c) diff --git a/.ci/dependencies_macports.txt b/.ci/dependencies_macports.txt index b23ac441d5..e530871ae7 100644 --- a/.ci/dependencies_macports.txt +++ b/.ci/dependencies_macports.txt @@ -16,3 +16,4 @@ ghostscript libslirp vde2 libsndfile +libserialport diff --git a/.ci/dependencies_msys.txt b/.ci/dependencies_msys.txt index eacdb8b36f..b6c0979e27 100644 --- a/.ci/dependencies_msys.txt +++ b/.ci/dependencies_msys.txt @@ -14,3 +14,4 @@ qt5-static qt5-translations vulkan-headers libsndfile +libserialport diff --git a/.github/workflows/cmake_linux.yml b/.github/workflows/cmake_linux.yml index a8945172b1..73ae94969b 100644 --- a/.github/workflows/cmake_linux.yml +++ b/.github/workflows/cmake_linux.yml @@ -82,6 +82,7 @@ jobs: libslirp-dev libfluidsynth-dev libvdeplug-dev + libserialport-dev ${{ matrix.ui.packages }} - name: Checkout repository @@ -90,7 +91,7 @@ jobs: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Install sonar-scanner and build-wrapper - uses: SonarSource/sonarcloud-github-c-cpp@5c3c39143e381909307f6903f13774b275ed956d + uses: SonarSource/sonarcloud-github-c-cpp@v3 - name: Configure CMake run: >- diff --git a/.github/workflows/cmake_macos.yml b/.github/workflows/cmake_macos.yml index d02ec595b7..5a04543859 100644 --- a/.github/workflows/cmake_macos.yml +++ b/.github/workflows/cmake_macos.yml @@ -82,6 +82,7 @@ jobs: fluidsynth libslirp vde + libserialport ${{ matrix.ui.packages }} - name: Checkout repository @@ -90,7 +91,7 @@ jobs: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Install sonar-scanner and build-wrapper - uses: SonarSource/sonarcloud-github-c-cpp@5c3c39143e381909307f6903f13774b275ed956d + uses: SonarSource/sonarcloud-github-c-cpp@v3 - name: Configure CMake run: >- @@ -102,6 +103,7 @@ jobs: -D Qt5_ROOT=$(brew --prefix qt@5) -D Qt5LinguistTools_ROOT=$(brew --prefix qt@5) -D OpenAL_ROOT=$(brew --prefix openal-soft) + -D LIBSERIALPORT_ROOT=$(brew --prefix libserialport) - name: Build run: | @@ -181,6 +183,7 @@ jobs: openal-soft fluidsynth libslirp + libserialport ${{ matrix.ui.packages }} - name: Checkout repository @@ -189,7 +192,7 @@ jobs: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis # - name: Install sonar-scanner and build-wrapper -# uses: SonarSource/sonarcloud-github-c-cpp@5c3c39143e381909307f6903f13774b275ed956d +# uses: SonarSource/sonarcloud-github-c-cpp@v3 - name: Configure CMake run: >- @@ -201,6 +204,7 @@ jobs: -D Qt5_ROOT=$(brew --prefix qt@5) -D Qt5LinguistTools_ROOT=$(brew --prefix qt@5) -D OpenAL_ROOT=$(brew --prefix openal-soft) + -D LIBSERIALPORT_ROOT=$(brew --prefix libserialport) - name: Build run: | diff --git a/.github/workflows/cmake_windows_msys2.yml b/.github/workflows/cmake_windows_msys2.yml index 7620351b38..abe25b949a 100644 --- a/.github/workflows/cmake_windows_msys2.yml +++ b/.github/workflows/cmake_windows_msys2.yml @@ -97,6 +97,7 @@ jobs: rtmidi:p libslirp:p fluidsynth:p + libserialport:p ${{ matrix.ui.packages }} - name: Checkout repository @@ -105,7 +106,7 @@ jobs: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Install sonar-scanner and build-wrapper - uses: SonarSource/sonarcloud-github-c-cpp@5c3c39143e381909307f6903f13774b275ed956d + uses: SonarSource/sonarcloud-github-c-cpp@v3 - name: Configure CMake run: >- diff --git a/.github/workflows/codeql_linux.yml b/.github/workflows/codeql_linux.yml index 412a2045c0..fc03977030 100644 --- a/.github/workflows/codeql_linux.yml +++ b/.github/workflows/codeql_linux.yml @@ -85,6 +85,7 @@ jobs: libslirp-dev libfluidsynth-dev libvdeplug-dev + libserialport-dev ${{ matrix.ui.packages }} - name: Checkout repository diff --git a/.github/workflows/codeql_macos.yml b/.github/workflows/codeql_macos.yml index 5ce12548b2..841ca98f52 100644 --- a/.github/workflows/codeql_macos.yml +++ b/.github/workflows/codeql_macos.yml @@ -76,6 +76,7 @@ jobs: fluidsynth libslirp vde + libserialport ${{ matrix.ui.packages }} - name: Checkout repository @@ -97,6 +98,7 @@ jobs: -D Qt5_ROOT=$(brew --prefix qt@5) -D Qt5LinguistTools_ROOT=$(brew --prefix qt@5) -D OpenAL_ROOT=$(brew --prefix openal-soft) + -D LIBSERIALPORT_ROOT=$(brew --prefix libserialport) - name: Build run: cmake --build build diff --git a/.github/workflows/codeql_windows_msys2.yml b/.github/workflows/codeql_windows_msys2.yml index 67de8ec8b3..7a00559101 100644 --- a/.github/workflows/codeql_windows_msys2.yml +++ b/.github/workflows/codeql_windows_msys2.yml @@ -102,6 +102,7 @@ jobs: rtmidi:p libslirp:p fluidsynth:p + libserialport:p ${{ matrix.ui.packages }} - name: Checkout repository diff --git a/CMakePresets.json b/CMakePresets.json index c19a7abc0d..d4af8e6cb4 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -68,7 +68,8 @@ "Qt5_DIR": "/opt/homebrew/opt/qt@5/lib/cmake/Qt5", "MOLTENVK_DIR": "/opt/homebrew/opt/molten-vk", "Qt5LinguistTools_DIR": "/opt/homebrew/opt/qt@5/lib/cmake/Qt5LinguistTools", - "OpenAL_ROOT": "/opt/homebrew/opt/openal-soft" + "OpenAL_ROOT": "/opt/homebrew/opt/openal-soft", + "LIBSERIALPORT_ROOT": "/opt/homebrew/opt/libserialport" }, "inherits": "regular" }, @@ -86,6 +87,7 @@ "MOLTENVK_DIR": "/opt/homebrew/opt/molten-vk", "Qt5LinguistTools_DIR": "/opt/homebrew/opt/qt@5/lib/cmake/Qt5LinguistTools", "OpenAL_ROOT": "/opt/homebrew/opt/openal-soft", + "LIBSERIALPORT_ROOT": "/opt/homebrew/opt/libserialport", "CMAKE_CXX_FLAGS_DEBUG": "-g -O0 -DENABLE_VDE_LOG", "CMAKE_C_FLAGS_DEBUG": "-g -O0 -DENABLE_VDE_LOG" }, diff --git a/debian/control b/debian/control index a718aee337..a11e2af486 100644 --- a/debian/control +++ b/debian/control @@ -16,7 +16,8 @@ Build-Depends: cmake (>= 3.21), libsndfile-dev, ninja-build, qttools5-dev, - qtbase5-private-dev + qtbase5-private-dev, + libserialport-dev Standards-Version: 4.6.0 Homepage: https://86box.net/ #Vcs-Browser: https://salsa.debian.org/debian/86box diff --git a/src/config.c b/src/config.c index 69cfb7e9bb..ec620bf9e9 100644 --- a/src/config.c +++ b/src/config.c @@ -897,9 +897,9 @@ load_storage_controllers(void) fatal("load_storage_controllers(): strlen(p) > 2047 " "(cassette_image_history[%i])\n", i); else - snprintf(cassette_image_history[i], (MAX_IMAGE_PATH_LEN - 1), "%s", p); + snprintf(cassette_image_history[i], MAX_IMAGE_PATH_LEN, "%s", p); } else - snprintf(cassette_image_history[i], (MAX_IMAGE_PATH_LEN - 1), "%s%s%s", usr_path, + snprintf(cassette_image_history[i], MAX_IMAGE_PATH_LEN, "%s%s%s", usr_path, path_get_slash(usr_path), p); path_normalize(cassette_image_history[i]); } @@ -962,9 +962,9 @@ load_storage_controllers(void) fatal("load_storage_controllers(): strlen(p) > 2047 " "(cart_image_history[%i][%i])\n", c, i); else - snprintf(cart_image_history[c][i], (MAX_IMAGE_PATH_LEN - 1), "%s", p); + snprintf(cart_image_history[c][i], MAX_IMAGE_PATH_LEN, "%s", p); } else - snprintf(cart_image_history[c][i], (MAX_IMAGE_PATH_LEN - 1), "%s%s%s", usr_path, + snprintf(cart_image_history[c][i], MAX_IMAGE_PATH_LEN, "%s%s%s", usr_path, path_get_slash(usr_path), p); path_normalize(cart_image_history[c][i]); } @@ -1261,9 +1261,9 @@ load_floppy_and_cdrom_drives(void) fatal("load_floppy_and_cdrom_drives(): strlen(p) > 2047 " "(fdd_image_history[%i][%i])\n", c, i); else - snprintf(fdd_image_history[c][i], (MAX_IMAGE_PATH_LEN - 1), "%s", p); + snprintf(fdd_image_history[c][i], MAX_IMAGE_PATH_LEN, "%s", p); } else - snprintf(fdd_image_history[c][i], (MAX_IMAGE_PATH_LEN - 1), "%s%s%s", usr_path, + snprintf(fdd_image_history[c][i], MAX_IMAGE_PATH_LEN, "%s%s%s", usr_path, path_get_slash(usr_path), p); path_normalize(fdd_image_history[c][i]); } @@ -1372,9 +1372,9 @@ load_floppy_and_cdrom_drives(void) fatal("load_floppy_and_cdrom_drives(): strlen(p) > 2047 " "(cdrom[%i].image_history[%i])\n", c, i); else - snprintf(cdrom[c].image_history[i], (MAX_IMAGE_PATH_LEN - 1), "%s", p); + snprintf(cdrom[c].image_history[i], MAX_IMAGE_PATH_LEN, "%s", p); } else - snprintf(cdrom[c].image_history[i], (MAX_IMAGE_PATH_LEN - 1), "%s%s%s", usr_path, + snprintf(cdrom[c].image_history[i], MAX_IMAGE_PATH_LEN, "%s%s%s", usr_path, path_get_slash(usr_path), p); path_normalize(cdrom[c].image_history[i]); } @@ -1502,9 +1502,9 @@ load_other_removable_devices(void) fatal("load_other_removable_devices(): strlen(p) > 2047 " "(zip_drives[%i].image_history[%i])\n", c, i); else - snprintf(zip_drives[c].image_history[i], (MAX_IMAGE_PATH_LEN - 1), "%s", p); + snprintf(zip_drives[c].image_history[i], MAX_IMAGE_PATH_LEN, "%s", p); } else - snprintf(zip_drives[c].image_history[i], (MAX_IMAGE_PATH_LEN - 1), "%s%s%s", usr_path, + snprintf(zip_drives[c].image_history[i], MAX_IMAGE_PATH_LEN, "%s%s%s", usr_path, path_get_slash(usr_path), p); path_normalize(zip_drives[c].image_history[i]); } @@ -1615,9 +1615,9 @@ load_other_removable_devices(void) fatal("load_other_removable_devices(): strlen(p) > 2047 " "(mo_drives[%i].image_history[%i])\n", c, i); else - snprintf(mo_drives[c].image_history[i], (MAX_IMAGE_PATH_LEN - 1), "%s", p); + snprintf(mo_drives[c].image_history[i], MAX_IMAGE_PATH_LEN, "%s", p); } else - snprintf(mo_drives[c].image_history[i], (MAX_IMAGE_PATH_LEN - 1), "%s%s%s", usr_path, + snprintf(mo_drives[c].image_history[i], MAX_IMAGE_PATH_LEN, "%s%s%s", usr_path, path_get_slash(usr_path), p); path_normalize(mo_drives[c].image_history[i]); } diff --git a/src/device/serial.c b/src/device/serial.c index 2b832f7506..dcaff0f7f4 100644 --- a/src/device/serial.c +++ b/src/device/serial.c @@ -197,6 +197,8 @@ serial_receive_timer(void *priv) /* Raise Data Ready interrupt. */ dev->lsr |= 0x01; dev->int_status |= SERIAL_INT_RECEIVE; + if (dev->lsr & 0x02) + dev->int_status |= SERIAL_INT_LSR; serial_update_ints(dev); } @@ -266,10 +268,6 @@ serial_move_to_txsr(serial_t *dev) /* Update interrupts to signal THRE and that TXSR is no longer empty. */ serial_update_ints(dev); } - if (dev->transmit_enabled & 2) - dev->baud_cycles++; - else - dev->baud_cycles = 0; /* If not moving while transmitting, reset BAUDOUT cycle count. */ if (!dev->fifo_enabled || (fifo_get_count(dev->xmit_fifo) == 0x0)) dev->transmit_enabled &= ~1; /* Stop moving. */ dev->transmit_enabled |= 2; /* Start transmitting. */ @@ -303,16 +301,26 @@ static void serial_transmit_timer(void *priv) { serial_t *dev = (serial_t *) priv; - int delay = 8; /* STOP to THRE delay is 8 BAUDOUT cycles. */ + /* + Norton Diagnostics waits for up to 2 bit periods, this is + confirmed by the NS16550A timings graph, which shows operation + as follows after write: 1 bit of delay, then start bit, and at + the end of the start bit, move from THR to TXSR. + */ + int delay = 1; if (dev->transmit_enabled & 3) { + /* + If already transmitting, move from THR to TXSR at the end of + the last data bit. + */ if ((dev->transmit_enabled & 1) && (dev->transmit_enabled & 2)) - delay = dev->data_bits; /* Delay by less if already transmitting. */ + delay = dev->data_bits + 1; dev->baud_cycles++; - /* We have processed (total bits) BAUDOUT cycles, transmit the byte. */ - if ((dev->baud_cycles == dev->bits) && (dev->transmit_enabled & 2)) + /* We have processed (delay + total bits) BAUDOUT cycles, transmit the byte. */ + if ((dev->baud_cycles == (dev->bits + 1)) && (dev->transmit_enabled & 2)) serial_process_txsr(dev); /* We have processed (data bits) BAUDOUT cycles. */ @@ -614,6 +622,11 @@ serial_write(uint16_t addr, uint8_t val, void *priv) dev->msr = new_msr; + if (dev->msr & 0x0f) { + dev->int_status |= SERIAL_INT_MSR; + serial_update_ints(dev); + } + /* TODO: Why reset the FIFO's here?! */ fifo_reset(dev->xmit_fifo); fifo_reset(dev->rcvr_fifo); diff --git a/src/disk/hdc_ide.c b/src/disk/hdc_ide.c index 660093a8e1..b868f51a0d 100644 --- a/src/disk/hdc_ide.c +++ b/src/disk/hdc_ide.c @@ -558,7 +558,10 @@ ide_hd_identify(const ide_t *ide) /* Firmware */ ide_padstr((char *) (ide->buffer + 23), EMU_VERSION_EX, 8); /* Model */ - ide_padstr((char *) (ide->buffer + 27), device_identify, 40); + if (hdd[ide->hdd_num].model) + ide_padstr((char *) (ide->buffer + 27), hdd[ide->hdd_num].model, 40); + else + ide_padstr((char *) (ide->buffer + 27), device_identify, 40); /* Fixed drive */ ide->buffer[0] = (1 << 6); /* Buffer type */ diff --git a/src/disk/hdd.c b/src/disk/hdd.c index 7acfc82be1..ea38602d29 100644 --- a/src/disk/hdd.c +++ b/src/disk/hdd.c @@ -415,7 +415,10 @@ static hdd_preset_t hdd_speed_presets[] = { { .name = "[1997] 5400 RPM", .internal_name = "1997_5400rpm", .zones = 16, .avg_spt = 185, .heads = 6, .rpm = 5400, .full_stroke_ms = 20, .track_seek_ms = 2.5, .rcache_num_seg = 8, .rcache_seg_size = 64, .max_multiple = 32 }, { .name = "[1998] 5400 RPM", .internal_name = "1998_5400rpm", .zones = 16, .avg_spt = 300, .heads = 8, .rpm = 5400, .full_stroke_ms = 20, .track_seek_ms = 2, .rcache_num_seg = 8, .rcache_seg_size = 128, .max_multiple = 32 }, { .name = "[2000] 7200 RPM", .internal_name = "2000_7200rpm", .zones = 16, .avg_spt = 350, .heads = 6, .rpm = 7200, .full_stroke_ms = 15, .track_seek_ms = 2, .rcache_num_seg = 16, .rcache_seg_size = 128, .max_multiple = 32 }, - // clang-format on + { .name = "Conner CP3024", .internal_name = "CP3024", .model = "Conner Peripherals 20MB - CP3024", .zones = 1, .avg_spt = 33, .heads = 2, .rpm = 3500, .full_stroke_ms = 50, .track_seek_ms = 8, .rcache_num_seg = 1, .rcache_seg_size = 8, .max_multiple = 8 }, + { .name = "Conner CP3044", .internal_name = "CP3044", .model = "Conner Peripherals 40MB - CP3044", .zones = 1, .avg_spt = 40, .heads = 2, .rpm = 3500, .full_stroke_ms = 50, .track_seek_ms = 8, .rcache_num_seg = 1, .rcache_seg_size = 8, .max_multiple = 8 }, + { .name = "Conner CP3104", .internal_name = "CP3104", .model = "Conner Peripherals 104MB - CP3104", .zones = 1, .avg_spt = 33, .heads = 8, .rpm = 3500, .full_stroke_ms = 45, .track_seek_ms = 8, .rcache_num_seg = 4, .rcache_seg_size = 8, .max_multiple = 8 }, + // clang-format on }; int @@ -472,6 +475,8 @@ hdd_preset_apply(int hdd_id) hd->cache.num_segments = preset->rcache_num_seg; hd->cache.segment_size = preset->rcache_seg_size; hd->max_multiple_block = preset->max_multiple; + if (preset->model) + hd->model = preset->model; if (!hd->speed_preset) return; diff --git a/src/floppy/fdc.c b/src/floppy/fdc.c index 7426bc2169..546174c57b 100644 --- a/src/floppy/fdc.c +++ b/src/floppy/fdc.c @@ -652,13 +652,31 @@ fdc_sis(fdc_t *fdc) fdc->paramstogo = 2; } +static void +fdc_soft_reset(fdc_t *fdc) +{ + if (fdc->power_down) { + timer_set_delay_u64(&fdc->timer, 1000 * TIMER_USEC); + fdc->interrupt = -5; + } else { + timer_set_delay_u64(&fdc->timer, 8 * TIMER_USEC); + fdc->interrupt = -1; + + fdc->perp &= 0xfc; + + for (int i = 0; i < FDD_NUM; i++) + ui_sb_update_icon(SB_FLOPPY | i, 0); + + fdc_ctrl_reset(fdc); + } +} + static void fdc_write(uint16_t addr, uint8_t val, void *priv) { fdc_t *fdc = (fdc_t *) priv; int drive; - int i; int drive_num; fdc_log("Write FDC %04X %02X\n", addr, val); @@ -682,7 +700,6 @@ fdc_write(uint16_t addr, uint8_t val, void *priv) fdc->interrupt = -1; ui_sb_update_icon(SB_FLOPPY | 0, 0); fdc_ctrl_reset(fdc); - fdd_changed[0] = 1; } if (!fdd_get_flags(0)) val &= 0xfe; @@ -699,24 +716,10 @@ fdc_write(uint16_t addr, uint8_t val, void *priv) fdc->stat = 0x00; fdc->pnum = fdc->ptot = 0; } - if ((val & 4) && !(fdc->dor & 4)) { - if (fdc->power_down) { - timer_set_delay_u64(&fdc->timer, 1000 * TIMER_USEC); - fdc->interrupt = -5; - } else { - timer_set_delay_u64(&fdc->timer, 8 * TIMER_USEC); - fdc->interrupt = -1; - - fdc->perp &= 0xfc; - - for (i = 0; i < FDD_NUM; i++) - ui_sb_update_icon(SB_FLOPPY | i, 0); - - fdc_ctrl_reset(fdc); - } - } + if ((val & 4) && !(fdc->dor & 4)) + fdc_soft_reset(fdc); /* We can now simplify this since each motor now spins separately. */ - for (i = 0; i < FDD_NUM; i++) { + for (int i = 0; i < FDD_NUM; i++) { drive_num = real_drive(fdc, i); if ((!fdd_get_flags(drive_num)) || (drive_num >= FDD_NUM)) val &= ~(0x10 << drive_num); @@ -735,28 +738,14 @@ fdc_write(uint16_t addr, uint8_t val, void *priv) fdc_update_rwc(fdc, drive, (val & 0x30) >> 4); } return; - case 4: + case 4: /* DSR */ if (!(fdc->flags & FDC_FLAG_NO_DSR_RESET)) { if (!(val & 0x80)) { timer_set_delay_u64(&fdc->timer, 8 * TIMER_USEC); fdc->interrupt = -6; } - if (fdc->power_down || ((val & 0x80) && !(fdc->dsr & 0x80))) { - if (fdc->power_down) { - timer_set_delay_u64(&fdc->timer, 1000 * TIMER_USEC); - fdc->interrupt = -5; - } else { - timer_set_delay_u64(&fdc->timer, 8 * TIMER_USEC); - fdc->interrupt = -1; - - fdc->perp &= 0xfc; - - for (i = 0; i < FDD_NUM; i++) - ui_sb_update_icon(SB_FLOPPY | i, 0); - - fdc_ctrl_reset(fdc); - } - } + if (fdc->power_down || ((val & 0x80) && !(fdc->dsr & 0x80))) + fdc_soft_reset(fdc); } fdc->dsr = val; return; diff --git a/src/floppy/fdd_86f.c b/src/floppy/fdd_86f.c index cf9e836943..0e8a6590ab 100644 --- a/src/floppy/fdd_86f.c +++ b/src/floppy/fdd_86f.c @@ -2911,6 +2911,7 @@ d86f_decompose_encoded_buffer(int drive, int side) uint16_t temp2; uint32_t len; const uint16_t *dst = dev->track_encoded_data[side]; + const uint16_t *dst_s = dev->track_surface_data[side]; uint16_t *src1 = dev->thin_track_encoded_data[0][side]; uint16_t *src1_s = dev->thin_track_surface_data[0][side]; uint16_t *src2 = dev->thin_track_encoded_data[1][side]; @@ -2922,12 +2923,13 @@ d86f_decompose_encoded_buffer(int drive, int side) if (d86f_has_surface_desc(drive)) { /* Source image has surface description data, so we have some more handling to do. We need hole masks for both buffers. Holes have data bit clear and surface bit set. */ - temp = ~src1[i] & src1_s[i]; - temp2 = ~src2[i] & src2_s[i]; - src1[i] = dst[i] & ~temp; - src1_s[i] = temp; - src2[i] = dst[i] & ~temp2; - src2_s[i] = temp2; + src1_s[i] = src2_s[i] = dst_s[i]; /* Write the new holes and weak bits. */ + temp = ~src1[i] & src1_s[i]; /* Bits that are clear in data and set in surface are holes. */ + temp2 = ~src2[i] & src2_s[i]; /* Bits that are clear in data and set in surface are holes. */ + src1[i] = dst[i] & ~temp; /* Make sure the holes' bits are cleared in the decomposed buffer. */ + src1_s[i] |= temp; /* Make sure the holes' bits are set in the decomposed surface. */ + src2[i] = dst[i] & ~temp2; /* Make sure the holes' bits are cleared in the decomposed buffer. */ + src2_s[i] |= temp2; /* Make sure the holes' bits are set in the decomposed surface. */ } else src1[i] = src2[i] = dst[i]; } diff --git a/src/game/gameport.c b/src/game/gameport.c index 58d9a446f5..e7495e3654 100644 --- a/src/game/gameport.c +++ b/src/game/gameport.c @@ -13,10 +13,12 @@ * Authors: Miran Grca, * Sarah Walker, * RichardG, + * Jasmine Iwanek, * - * Copyright 2016-2018 Miran Grca. + * Copyright 2016-2022 Miran Grca. * Copyright 2008-2018 Sarah Walker. * Copyright 2021 RichardG. + * Copyright 2021-2024 Jasmine Iwanek. */ #include #include @@ -468,7 +470,7 @@ const device_t gameport_device = { .name = "Game port", .internal_name = "gameport", .flags = 0, - .local = 0x080200, + .local = GAMEPORT_8ADDR | 0x0200, .init = gameport_init, .close = gameport_close, .reset = NULL, @@ -482,7 +484,7 @@ const device_t gameport_201_device = { .name = "Game port (Port 201h only)", .internal_name = "gameport_201", .flags = 0, - .local = 0x010201, + .local = GAMEPORT_1ADDR | 0x0201, .init = gameport_init, .close = gameport_close, .reset = NULL, @@ -496,7 +498,7 @@ const device_t gameport_203_device = { .name = "Game port (Port 203h only)", .internal_name = "gameport_203", .flags = 0, - .local = 0x010203, + .local = GAMEPORT_1ADDR | 0x0203, .init = gameport_init, .close = gameport_close, .reset = NULL, @@ -510,7 +512,7 @@ const device_t gameport_205_device = { .name = "Game port (Port 205h only)", .internal_name = "gameport_205", .flags = 0, - .local = 0x010205, + .local = GAMEPORT_1ADDR | 0x0205, .init = gameport_init, .close = gameport_close, .reset = NULL, @@ -524,7 +526,7 @@ const device_t gameport_207_device = { .name = "Game port (Port 207h only)", .internal_name = "gameport_207", .flags = 0, - .local = 0x010207, + .local = GAMEPORT_1ADDR | 0x0207, .init = gameport_init, .close = gameport_close, .reset = NULL, @@ -538,7 +540,7 @@ const device_t gameport_208_device = { .name = "Game port (Port 208h-20fh)", .internal_name = "gameport_208", .flags = 0, - .local = 0x080208, + .local = GAMEPORT_8ADDR | 0x0208, .init = gameport_init, .close = gameport_close, .reset = NULL, @@ -552,7 +554,7 @@ const device_t gameport_209_device = { .name = "Game port (Port 209h only)", .internal_name = "gameport_209", .flags = 0, - .local = 0x010209, + .local = GAMEPORT_1ADDR | 0x0209, .init = gameport_init, .close = gameport_close, .reset = NULL, @@ -566,7 +568,7 @@ const device_t gameport_20b_device = { .name = "Game port (Port 20Bh only)", .internal_name = "gameport_20b", .flags = 0, - .local = 0x01020B, + .local = GAMEPORT_1ADDR | 0x020B, .init = gameport_init, .close = gameport_close, .reset = NULL, @@ -580,7 +582,7 @@ const device_t gameport_20d_device = { .name = "Game port (Port 20Dh only)", .internal_name = "gameport_20d", .flags = 0, - .local = 0x01020D, + .local = GAMEPORT_1ADDR | 0x020D, .init = gameport_init, .close = gameport_close, .reset = NULL, @@ -594,7 +596,7 @@ const device_t gameport_20f_device = { .name = "Game port (Port 20Fh only)", .internal_name = "gameport_20f", .flags = 0, - .local = 0x01020F, + .local = GAMEPORT_1ADDR | 0x020F, .init = gameport_init, .close = gameport_close, .reset = NULL, @@ -662,7 +664,7 @@ const device_t gameport_pnp_device = { .name = "Game port (Plug and Play only)", .internal_name = "gameport_pnp", .flags = 0, - .local = 0x080000, + .local = GAMEPORT_8ADDR, .init = gameport_init, .close = gameport_close, .reset = NULL, @@ -676,7 +678,7 @@ const device_t gameport_pnp_6io_device = { .name = "Game port (Plug and Play only, 6 I/O ports)", .internal_name = "gameport_pnp_6io", .flags = 0, - .local = 0x060000, + .local = GAMEPORT_6ADDR, .init = gameport_init, .close = gameport_close, .reset = NULL, @@ -690,7 +692,7 @@ const device_t gameport_sio_device = { .name = "Game port (Super I/O)", .internal_name = "gameport_sio", .flags = 0, - .local = 0x1080000, + .local = GAMEPORT_SIO | GAMEPORT_8ADDR, .init = gameport_init, .close = gameport_close, .reset = NULL, @@ -704,7 +706,7 @@ const device_t gameport_sio_1io_device = { .name = "Game port (Super I/O, 1 I/O port)", .internal_name = "gameport_sio", .flags = 0, - .local = 0x1010000, + .local = GAMEPORT_SIO | GAMEPORT_1ADDR, .init = gameport_init, .close = gameport_close, .reset = NULL, diff --git a/src/include/86box/gameport.h b/src/include/86box/gameport.h index d9c702394f..576d6b8e78 100644 --- a/src/include/86box/gameport.h +++ b/src/include/86box/gameport.h @@ -13,10 +13,12 @@ * Authors: Miran Grca, * Sarah Walker, * RichardG, + * Jasmine Iwanek, * - * Copyright 2016-2018 Miran Grca. + * Copyright 2016-2022 Miran Grca. * Copyright 2008-2018 Sarah Walker. * Copyright 2021 RichardG. + * Copyright 2021-2024 Jasmine Iwanek. */ #ifndef EMU_GAMEPORT_H #define EMU_GAMEPORT_H @@ -45,6 +47,9 @@ #define JOYSTICK_PRESENT(n) (joystick_state[n].plat_joystick_nr != 0) +#define GAMEPORT_1ADDR 0x010000 +#define GAMEPORT_6ADDR 0x060000 +#define GAMEPORT_8ADDR 0x080000 #define GAMEPORT_SIO 0x1000000 typedef struct plat_joystick_t { diff --git a/src/include/86box/hdd.h b/src/include/86box/hdd.h index 25c32355ee..a4bded58f2 100644 --- a/src/include/86box/hdd.h +++ b/src/include/86box/hdd.h @@ -90,6 +90,7 @@ enum { typedef struct hdd_preset_t { const char *name; const char *internal_name; + const char *model; uint32_t zones; uint32_t avg_spt; uint32_t heads; @@ -165,6 +166,7 @@ typedef struct hard_disk_t { uint32_t spt; uint32_t hpc; /* Physical geometry parameters */ uint32_t tracks; + const char *model; hdd_zone_t zones[HDD_MAX_ZONES]; uint32_t num_zones; diff --git a/src/include/86box/machine.h b/src/include/86box/machine.h index 953ee7585d..2312d2920c 100644 --- a/src/include/86box/machine.h +++ b/src/include/86box/machine.h @@ -687,7 +687,7 @@ extern int machine_at_8500tuc_init(const machine_t *); extern int machine_at_p55t2s_init(const machine_t *); extern int machine_at_p5vxb_init(const machine_t *); -extern int machine_at_gw2kte_init(const machine_t *); +extern int machine_at_gw2kma_init(const machine_t *); extern int machine_at_ap5s_init(const machine_t *); extern int machine_at_pc140_6260_init(const machine_t *); @@ -722,6 +722,7 @@ extern int machine_at_pb680_init(const machine_t *); extern int machine_at_pb810_init(const machine_t *); extern int machine_at_mb520n_init(const machine_t *); extern int machine_at_i430vx_init(const machine_t *); +extern int machine_at_hitman_init(const machine_t *); extern int machine_at_ma23c_init(const machine_t *); extern int machine_at_nupro592_init(const machine_t *); @@ -848,6 +849,9 @@ extern int machine_at_vpc2007_init(const machine_t *); /* m_at_t3100e.c */ extern int machine_at_t3100e_init(const machine_t *); +/* m_at_grid.c */ +extern int machine_at_grid1520_init(const machine_t *); + /* m_elt.c */ extern int machine_elt_init(const machine_t *); diff --git a/src/include/86box/snd_opl.h b/src/include/86box/snd_opl.h index 441e2a1190..fe0112b474 100644 --- a/src/include/86box/snd_opl.h +++ b/src/include/86box/snd_opl.h @@ -18,12 +18,13 @@ #define SOUND_OPL_H enum fm_type { - FM_YM3812 = 0, /* OPL2 */ - FM_YMF262 = 1, /* OPL3 */ - FM_YMF289B = 2, /* OPL3-L */ - FM_YMF278B = 3, /* OPL 4 */ - FM_ESFM = 4, /* ESFM */ - FM_MAX = 5 + FM_YM3812 = 0, /* OPL2 */ + FM_YMF262 = 1, /* OPL3 */ + FM_YMF289B = 2, /* OPL3-L */ + FM_YMF278B = 3, /* OPL 4 */ + FM_ESFM = 4, /* ESFM */ + FM_OPL2BOARD = 5, /* OPL2BOARD (External Device)*/ + FM_MAX = 6 }; enum fm_driver { @@ -47,6 +48,7 @@ extern uint8_t fm_driver_get(int chip_id, fm_drv_t *drv); extern const fm_drv_t nuked_opl_drv; extern const fm_drv_t ymfm_drv; extern const fm_drv_t esfmu_opl_drv; +extern const fm_drv_t ymfm_opl2board_drv; #ifdef EMU_DEVICE_H extern const device_t ym3812_nuked_device; @@ -58,6 +60,10 @@ extern const device_t ymf289b_ymfm_device; extern const device_t ymf278b_ymfm_device; extern const device_t esfm_esfmu_device; +#ifdef USE_LIBSERIALPORT +extern const device_t ym_opl2board_device; +#endif + #endif #endif /*SOUND_OPL_H*/ diff --git a/src/include/86box/snd_resid.h b/src/include/86box/snd_resid.h index 4ddaf9b91e..c7e97ac0fb 100644 --- a/src/include/86box/snd_resid.h +++ b/src/include/86box/snd_resid.h @@ -4,7 +4,7 @@ #ifdef __cplusplus extern "C" { #endif -void *sid_init(void); +void *sid_init(uint8_t type); void sid_close(void *priv); void sid_reset(void *priv); uint8_t sid_read(uint16_t addr, void *priv); diff --git a/src/include/86box/sound.h b/src/include/86box/sound.h index 9895e73d76..2a41b98f89 100644 --- a/src/include/86box/sound.h +++ b/src/include/86box/sound.h @@ -192,6 +192,7 @@ extern const device_t ps1snd_device; /* Innovation SSI-2001 */ extern const device_t ssi2001_device; +extern const device_t entertainer_device; /* Pro Audio Spectrum Plus, 16, and 16D */ extern const device_t pasplus_device; @@ -209,6 +210,11 @@ extern const device_t tndy_device; extern const device_t wss_device; extern const device_t ncr_business_audio_device; +#ifdef USE_LIBSERIALPORT +/* External Audio device OPL2Board (Host Connected hardware)*/ +extern const device_t opl2board_device; +#endif + #endif #endif /*EMU_SOUND_H*/ diff --git a/src/machine/CMakeLists.txt b/src/machine/CMakeLists.txt index 55e0e01970..ff6a668010 100644 --- a/src/machine/CMakeLists.txt +++ b/src/machine/CMakeLists.txt @@ -34,6 +34,7 @@ add_library(mch OBJECT m_v86p.c m_at.c m_at_commodore.c + m_at_grid.c m_at_t3100e.c m_at_t3100e_vid.c m_ps1.c diff --git a/src/machine/m_at_grid.c b/src/machine/m_at_grid.c new file mode 100644 index 0000000000..2fc7571296 --- /dev/null +++ b/src/machine/m_at_grid.c @@ -0,0 +1,354 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * Implementation of the GRiD GRiDcase 1520 + * + * The GRiDcase 1520 is a 286-based portable. + * These are HDDs supported by GRiD1520 (and probably other 15XX) BIOS + * "CP3022",5 + * "CP3024",5, 615,4,17 BIOS table type 2 + * "CP344",6, + * "CP3044",9, 980,5,17 BIOS table type 17 + * "CP3042",9 + * "CP3104",7, 776,8,33 extended type 224 (separate entry outside BIOS table) + * The only way to run unpatched BIOS is to run exactly that (or larger) + * geometry and report model name correctly in response to IDENTYIFY command. + * Alternatively you can use RomBuster to patch the BIOS. + * https://classicbits.net/coding-and-software/my-software/rombuster/ + */ +#include +#include +#include +#include +#include <86box/86box.h> +#include "cpu.h" +#include <86box/timer.h> +#include <86box/device.h> +#include <86box/fdd.h> +#include <86box/fdc.h> +#include <86box/fdc_ext.h> +#include <86box/io.h> +#include <86box/keyboard.h> +#include <86box/machine.h> +#include <86box/mem.h> +#include <86box/rom.h> +#include <86box/vid_cga.h> + +#define GRID_APPROM_SELECT 0x440 +#define GRID_APPROM_ENABLE 0x405 + +/* +approm mapping regs? +XXX_7FA equ 7FAh +XXX_7F8 equ 7F8h +XXX_7F9 equ 7F9h +XXX_BD0 equ 0BD0h +XXX_BD1 equ 0BD1h +*/ + +#define GRID_EMS_PAGE_0 0x0258 +#define GRID_EMS_PAGE_1 0x4258 +#define GRID_EMS_PAGE_2 0x8258 +#define GRID_EMS_PAGE_3 0xC258 +#define GRID_TURBO 0x416 +#define GRID_UNUSED_424 0x424 +#define GRID_426 0x426 +#define GRID_HIGH_ENABLE 0xFFF +#define GRID_ROM_SUBSYSTEM 0x6F8 + +// EMS window +#define GRID_EMS_BASE 0xE0000 +#define GRID_EMS_PAGE_SIZE 0x4000 +#define GRID_EMS_PAGE_MASK 0x3FFF +#define GRID_EMS_PAGE_SHIFT 14 +// physical base of extended memory +#define GRID_EXTENDED_BASE 0xA0000 +#define GRID_1M 0x100000 + +typedef struct { + uint8_t grid_unknown; + uint8_t grid_unused_424; + uint8_t grid_426; + uint8_t grid_high_enable; + uint8_t grid_ems_page[4]; + mem_mapping_t grid_ems_mapping[4]; + uint8_t grid_turbo; + uint8_t grid_rom_enable; + uint8_t grid_rom_select; +} grid_t; + + +static uint32_t get_grid_ems_paddr(grid_t *dev, uint32_t addr) { + uint32_t slot = (addr >> GRID_EMS_PAGE_SHIFT) & 0x3; + uint32_t paddr = addr; + + if (dev->grid_ems_page[slot] & 0x80) + paddr = GRID_EXTENDED_BASE + ((uint32_t)(dev->grid_ems_page[slot] & 0x7F) << GRID_EMS_PAGE_SHIFT) + (addr & GRID_EMS_PAGE_MASK); + + return paddr; +} + +static void grid_ems_mem_write8(uint32_t addr, uint8_t val, void *priv) { + grid_t *dev = (grid_t *) priv; + + addr = get_grid_ems_paddr(dev, addr); + + if (addr < (mem_size << 10)) + ram[addr] = val; +} + +static uint8_t grid_ems_mem_read8(uint32_t addr, void *priv) { + grid_t *dev = (grid_t *) priv; + uint8_t val = 0xFF; + + addr = get_grid_ems_paddr(dev, addr); + + if (addr < (mem_size << 10)) + val = ram[addr]; + + return val; +} + +static void grid_ems_mem_write16(uint32_t addr, uint16_t val, void *priv) { + grid_t *dev = (grid_t *) priv; + + addr = get_grid_ems_paddr(dev, addr); + + if (addr < (mem_size << 10)) + *(uint16_t *)&(ram[addr]) = val; +} + +static uint16_t grid_ems_mem_read16(uint32_t addr, void *priv) { + grid_t *dev = (grid_t *) priv; + uint16_t val = 0xFFFF; + + addr = get_grid_ems_paddr(dev, addr); + + if (addr < (mem_size << 10)) + val = *(uint16_t *)&(ram[addr]); + + return val; +} + +static void grid_ems_update_mapping(grid_t *dev, uint32_t slot) { + uint32_t vaddr = GRID_EMS_BASE + (slot << GRID_EMS_PAGE_SHIFT); + if (dev->grid_ems_page[slot] & 0x80) { + uint32_t paddr; + mem_mapping_enable(&dev->grid_ems_mapping[slot]); + paddr = get_grid_ems_paddr(dev, vaddr); + mem_mapping_set_exec(&dev->grid_ems_mapping[slot], ram + paddr); + } else { + mem_mapping_disable(&dev->grid_ems_mapping[slot]); + } +} + +static void grid_io_write(uint16_t port, uint8_t val, void *priv) { + grid_t *dev = (grid_t *) priv; + + switch (port) { + case GRID_426: + dev->grid_426 = val; + break; + case GRID_UNUSED_424: + dev->grid_unused_424 = val; + break; + case GRID_ROM_SUBSYSTEM: + case GRID_ROM_SUBSYSTEM+1: + case GRID_ROM_SUBSYSTEM+2: + case GRID_ROM_SUBSYSTEM+3: + case GRID_ROM_SUBSYSTEM+4: + case GRID_ROM_SUBSYSTEM+5: + case GRID_ROM_SUBSYSTEM+6: + case GRID_ROM_SUBSYSTEM+7: + break; + case GRID_APPROM_SELECT: + dev->grid_rom_select = val; + break; + case GRID_APPROM_ENABLE: + dev->grid_rom_enable = val; + break; + case GRID_TURBO: + if ((dev->grid_turbo ^ val) & 1) { + dev->grid_turbo = val; + if (dev->grid_turbo) + cpu_dynamic_switch(cpu); + else + cpu_dynamic_switch(0); /* 286/6 */ + } + break; + case GRID_EMS_PAGE_0: + case GRID_EMS_PAGE_1: + case GRID_EMS_PAGE_2: + case GRID_EMS_PAGE_3: { + uint32_t slot = (port >> 14) & 0x3; + if (dev->grid_ems_page[slot] == val) + break; // no change + + dev->grid_ems_page[slot] = val; + if (dev->grid_high_enable & 0x1) + break; // XMS is enabled + grid_ems_update_mapping(dev, slot); + + flushmmucache(); + break; + } + case GRID_HIGH_ENABLE: { + if (((val ^ dev->grid_high_enable) & 0x1) == 0) + break; // no change + dev->grid_high_enable = val; + if (dev->grid_high_enable & 0x1) { + for (uint8_t i = 0; i < 4; i++) + mem_mapping_disable(&dev->grid_ems_mapping[i]); + mem_mapping_enable(&ram_high_mapping); + } else { + mem_mapping_disable(&ram_high_mapping); + for (uint8_t i = 0; i < 4; i++) + grid_ems_update_mapping(dev, i); + } + flushmmucache(); + break; + } + default: + break; + } +} + +static uint8_t grid_io_read(uint16_t port, void *priv) { + grid_t *dev = (grid_t *) priv; + + switch (port) { + case GRID_426: + return dev->grid_426; + break; + case GRID_UNUSED_424: + return dev->grid_unused_424; + break; + case GRID_ROM_SUBSYSTEM: + return 0x99; + break; + case GRID_ROM_SUBSYSTEM+1: + case GRID_ROM_SUBSYSTEM+2: + case GRID_ROM_SUBSYSTEM+3: + case GRID_ROM_SUBSYSTEM+4: + case GRID_ROM_SUBSYSTEM+5: + case GRID_ROM_SUBSYSTEM+6: + case GRID_ROM_SUBSYSTEM+7: + break; + case GRID_APPROM_SELECT: + return dev->grid_rom_select; + case GRID_APPROM_ENABLE: + return dev->grid_rom_enable; + case GRID_TURBO: + return dev->grid_turbo; + case GRID_HIGH_ENABLE: + return dev->grid_high_enable; + case GRID_EMS_PAGE_0: + case GRID_EMS_PAGE_1: + case GRID_EMS_PAGE_2: + case GRID_EMS_PAGE_3: { + uint32_t slot = (port >> 14) & 0x3; + + return dev->grid_ems_page[slot]; + } + default: + break; + } + + return 0xff; +} + +static void * +grid_init(const device_t *info) +{ + grid_t *dev = calloc(1, sizeof(grid_t)); + + io_sethandler(GRID_ROM_SUBSYSTEM, 0x0008, grid_io_read, NULL, NULL, grid_io_write, NULL, NULL, dev); + io_sethandler(GRID_UNUSED_424, 0x0001, grid_io_read, NULL, NULL, grid_io_write, NULL, NULL, dev); + io_sethandler(GRID_426, 0x0001, grid_io_read, NULL, NULL, grid_io_write, NULL, NULL, dev); + io_sethandler(GRID_APPROM_SELECT, 0x0001, grid_io_read, NULL, NULL, grid_io_write, NULL, NULL, dev); + io_sethandler(GRID_APPROM_ENABLE, 0x0001, grid_io_read, NULL, NULL, grid_io_write, NULL, NULL, dev); + io_sethandler(GRID_TURBO, 0x0001, grid_io_read, NULL, NULL, grid_io_write, NULL, NULL, dev); + dev->grid_turbo = 0x1; + + io_sethandler(GRID_HIGH_ENABLE, 0x0001, grid_io_read, NULL, NULL, grid_io_write, NULL, NULL, dev); + io_sethandler(GRID_EMS_PAGE_0, 0x0001, grid_io_read, NULL, NULL, grid_io_write, NULL, NULL, dev); + io_sethandler(GRID_EMS_PAGE_1, 0x0001, grid_io_read, NULL, NULL, grid_io_write, NULL, NULL, dev); + io_sethandler(GRID_EMS_PAGE_2, 0x0001, grid_io_read, NULL, NULL, grid_io_write, NULL, NULL, dev); + io_sethandler(GRID_EMS_PAGE_3, 0x0001, grid_io_read, NULL, NULL, grid_io_write, NULL, NULL, dev); + + dev->grid_high_enable = 1; + for (uint8_t slot = 0; slot < 4; slot++) { + dev->grid_ems_page[slot] = 0; + mem_mapping_add(&dev->grid_ems_mapping[slot], GRID_EMS_BASE + (slot << GRID_EMS_PAGE_SHIFT), GRID_EMS_PAGE_SIZE, grid_ems_mem_read8, grid_ems_mem_read16, NULL, + grid_ems_mem_write8, grid_ems_mem_write16, NULL, ram + GRID_EXTENDED_BASE + (slot << GRID_EMS_PAGE_SHIFT), MEM_MAPPING_EXTERNAL, dev); + mem_mapping_disable(&dev->grid_ems_mapping[slot]); + } + flushmmucache(); + return dev; +} + +static void grid_close(void *priv) { + grid_t *dev = (grid_t *) priv; + + free(dev); +} + +static void grid_reset(void *priv) { + grid_t *dev = (grid_t *) priv; + + dev->grid_high_enable = 1; + mem_mapping_enable(&ram_high_mapping); + dev->grid_turbo = 0x1; + for (uint8_t slot = 0; slot < 4; slot++) { + dev->grid_ems_page[slot] = 0; + mem_mapping_disable(&dev->grid_ems_mapping[slot]); + } + flushmmucache(); + dev->grid_unknown = 0; + dev->grid_unused_424 = 0; + dev->grid_426 = 0; + dev->grid_rom_enable = 0; + dev->grid_rom_select = 0; +} + +const device_t grid_device = { + .name = "GRiDcase 1520 chipset", + .internal_name = "grid1520", + .flags = 0, + .local = 0, + .init = grid_init, + .close = grid_close, + .reset = grid_reset, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + +int machine_at_grid1520_init(const machine_t *model) { + int ret = 0; + + ret = bios_load_linear("roms/machines/grid1520/grid1520_891025.rom", + 0x000f8000, 0x8000, 0); + if (bios_only || !ret) + return ret; + + machine_at_common_ide_init(model); + mem_remap_top(384); + + device_add(&keyboard_at_device); + // for now just select CGA with amber monitor + //device_add(&cga_device); + + if (fdc_current[0] == FDC_INTERNAL) + device_add(&fdc_at_device); + + device_add(&grid_device); + + return ret; +} diff --git a/src/machine/m_at_socket7.c b/src/machine/m_at_socket7.c index d7e2840a97..31afb0f01c 100644 --- a/src/machine/m_at_socket7.c +++ b/src/machine/m_at_socket7.c @@ -824,6 +824,39 @@ machine_at_i430vx_init(const machine_t *model) return ret; } +int +machine_at_hitman_init(const machine_t *model) +{ + int ret; + + ret = bios_load_linear_combined2("roms/machines/hitman/1008CY1T.BIO", + "roms/machines/hitman/1008CY1T.BI1", + "roms/machines/hitman/1008CY1T.BI2", + "roms/machines/hitman/1008CY1T.BI3", + "roms/machines/hitman/1008CY1T.RCV", + 0x3a000, 128); + + if (bios_only || !ret) + return ret; + + machine_at_common_init_ex(model, 2); + + pci_init(PCI_CONFIG_TYPE_1); + pci_register_slot(0x00, PCI_CARD_NORTHBRIDGE, 0, 0, 0, 0); + pci_register_slot(0x08, PCI_CARD_VIDEO, 4, 0, 0, 0); + pci_register_slot(0x0D, PCI_CARD_NORMAL, 1, 2, 3, 4); + pci_register_slot(0x0E, PCI_CARD_NORMAL, 2, 3, 4, 1); + pci_register_slot(0x0F, PCI_CARD_NORMAL, 3, 4, 1, 2); + pci_register_slot(0x10, PCI_CARD_NORMAL, 4, 1, 2, 3); + pci_register_slot(0x07, PCI_CARD_SOUTHBRIDGE, 0, 0, 0, 4); + device_add(&i430vx_device); + device_add(&piix3_device); + device_add(&fdc37c932fr_device); + device_add(&intel_flash_bxt_ami_device); + + return ret; +} + int machine_at_ma23c_init(const machine_t *model) { diff --git a/src/machine/m_at_socket7_3v.c b/src/machine/m_at_socket7_3v.c index c36fade865..40ae221a0d 100644 --- a/src/machine/m_at_socket7_3v.c +++ b/src/machine/m_at_socket7_3v.c @@ -671,15 +671,15 @@ machine_at_p5vxb_init(const machine_t *model) } int -machine_at_gw2kte_init(const machine_t *model) +machine_at_gw2kma_init(const machine_t *model) { int ret; - ret = bios_load_linear_combined2("roms/machines/gw2kte/1008CY1T.BIO", - "roms/machines/gw2kte/1008CY1T.BI1", - "roms/machines/gw2kte/1008CY1T.BI2", - "roms/machines/gw2kte/1008CY1T.BI3", - "roms/machines/gw2kte/1008CY1T.RCV", + ret = bios_load_linear_combined2("roms/machines/gw2kma/1007DQ0T.BIO", + "roms/machines/gw2kma/1007DQ0T.BI1", + "roms/machines/gw2kma/1007DQ0T.BI2", + "roms/machines/gw2kma/1007DQ0T.BI3", + "roms/machines/gw2kma/1007DQ0T.RCV", 0x3a000, 128); if (bios_only || !ret) @@ -689,7 +689,6 @@ machine_at_gw2kte_init(const machine_t *model) pci_init(PCI_CONFIG_TYPE_1); pci_register_slot(0x00, PCI_CARD_NORTHBRIDGE, 0, 0, 0, 0); - pci_register_slot(0x08, PCI_CARD_VIDEO, 4, 0, 0, 0); pci_register_slot(0x0D, PCI_CARD_NORMAL, 1, 2, 3, 4); pci_register_slot(0x0E, PCI_CARD_NORMAL, 2, 3, 4, 1); pci_register_slot(0x0F, PCI_CARD_NORMAL, 3, 4, 1, 2); diff --git a/src/machine/machine_table.c b/src/machine/machine_table.c index 37a5dd3c3a..4028c9808f 100644 --- a/src/machine/machine_table.c +++ b/src/machine/machine_table.c @@ -3247,6 +3247,45 @@ const machine_t machines[] = { .snd_device = NULL, .net_device = NULL }, + { + .name = "[ISA] GRiD GRiDcase 1520", + .internal_name = "grid1520", + .type = MACHINE_TYPE_286, + .chipset = MACHINE_CHIPSET_PROPRIETARY, + .init = machine_at_grid1520_init, + .p1_handler = NULL, + .gpio_handler = NULL, + .available_flag = MACHINE_AVAILABLE, + .gpio_acpi_handler = NULL, + .cpu = { + .package = CPU_PKG_286, + .block = CPU_BLOCK_NONE, + .min_bus = 6000000, + .max_bus = 10000000, + .min_voltage = 0, + .max_voltage = 0, + .min_multi = 0, + .max_multi = 0 + }, + .bus_flags = MACHINE_AT, + .flags = MACHINE_IDE /*| MACHINE_VIDEO_FIXED*/, + .ram = { + .min = 1024, + .max = 8192, + .step = 1024 + }, + .nvrmask = 127, + .kbc_device = NULL, + .kbc_p1 = 0xff, + .gpio = 0xffffffff, + .gpio_acpi = 0xffffffff, + .device = NULL, + .fdc_device = NULL, + .sio_device = NULL, + .vid_device = NULL, + .snd_device = NULL, + .net_device = NULL + }, /* Has Quadtel KBC firmware. */ { .name = "[GC103] Quadtel 286 clone", @@ -10894,11 +10933,11 @@ const machine_t machines[] = { /* Has a SM(S)C FDC37C932FR Super I/O chip with on-chip KBC with AMI MegaKey (revision '5') KBC firmware. */ { - .name = "[i430VX] Gateway 2000 Tigereye", - .internal_name = "gw2kte", + .name = "[i430VX] Gateway 2000 Mailman", + .internal_name = "gw2kma", .type = MACHINE_TYPE_SOCKET7_3V, .chipset = MACHINE_CHIPSET_INTEL_430VX, - .init = machine_at_gw2kte_init, + .init = machine_at_gw2kma_init, .p1_handler = NULL, .gpio_handler = NULL, .available_flag = MACHINE_AVAILABLE, @@ -11914,6 +11953,51 @@ const machine_t machines[] = { .snd_device = NULL, .net_device = NULL }, + +/* 430VX */ + /* Has a SM(S)C FDC37C932FR Super I/O chip with on-chip KBC with AMI + MegaKey (revision '5') KBC firmware. */ + { + .name = "[i430VX] Gateway 2000 Hitman", + .internal_name = "hitman", + .type = MACHINE_TYPE_SOCKET7, + .chipset = MACHINE_CHIPSET_INTEL_430VX, + .init = machine_at_hitman_init, + .p1_handler = NULL, + .gpio_handler = NULL, + .available_flag = MACHINE_AVAILABLE, + .gpio_acpi_handler = NULL, + .cpu = { + .package = CPU_PKG_SOCKET5_7, + .block = CPU_BLOCK_NONE, + .min_bus = 50000000, + .max_bus = 66666667, + .min_voltage = 2200, + .max_voltage = 3520, + .min_multi = 1.5, + .max_multi = 3.0 + }, + .bus_flags = MACHINE_PS2_PCI | MACHINE_BUS_USB, + .flags = MACHINE_IDE_DUAL | MACHINE_APM | MACHINE_GAMEPORT | MACHINE_USB, + .ram = { + .min = 8192, + .max = 131072, + .step = 8192 + }, + .nvrmask = 511, + .kbc_device = NULL, + .kbc_p1 = 0xff, + .gpio = 0xffffffff, + .gpio_acpi = 0xffffffff, + .device = NULL, + .fdc_device = NULL, + .sio_device = NULL, + .vid_device = NULL, + .snd_device = NULL, + .net_device = NULL + }, + + /* Has a SM(S)C FDC37C935 Super I/O chip with on-chip KBC with Phoenix MultiKey/42 (version 1.38) KBC firmware. */ { diff --git a/src/qt/languages/pt-BR.po b/src/qt/languages/pt-BR.po index fa3fc94c11..f4e31f6aa4 100644 --- a/src/qt/languages/pt-BR.po +++ b/src/qt/languages/pt-BR.po @@ -799,7 +799,7 @@ msgid "Thrustmaster Flight Control System" msgstr "Sistema de Controle de Voo Thrustmaster" msgid "None" -msgstr "Nada" +msgstr "Nenhum" msgid "%u MB (CHS: %i, %i, %i)" msgstr "%u MB (CCS: %i, %i, %i)" diff --git a/src/sound/CMakeLists.txt b/src/sound/CMakeLists.txt index 832cdee3f2..a381051ba4 100644 --- a/src/sound/CMakeLists.txt +++ b/src/sound/CMakeLists.txt @@ -170,6 +170,28 @@ if(OPL4ML) target_compile_definitions(snd PRIVATE USE_OPL4ML) target_sources(snd PRIVATE midi_opl4.c midi_opl4_yrw801.c) endif() + +find_package(PkgConfig ) +pkg_check_modules(SERIALPORT libserialport) + +if(SERIALPORT_FOUND OR DEFINED LIBSERIALPORT_ROOT) + add_compile_definitions(USE_LIBSERIALPORT=1) + + if(APPLE) + include_directories(${LIBSERIALPORT_ROOT}/include) + target_link_libraries(86Box ${LIBSERIALPORT_ROOT}/lib/libserialport.dylib) + elseif(WIN32) + include_directories(${SERIALPORT_INCLUDE_DIRS}) + target_link_libraries(86Box ${SERIALPORT_LIBRARIES} SetupAPI) + else() + include_directories(${SERIALPORT_INCLUDE_DIRS}) + target_link_libraries(86Box ${SERIALPORT_LIBRARIES}) + endif() + target_sources(snd PRIVATE + snd_opl2board.c + snd_opl_opl2board.cpp +) +endif() add_subdirectory(resid-fp) target_link_libraries(86Box resid-fp) diff --git a/src/sound/resid-fp/CMakeLists.txt b/src/sound/resid-fp/CMakeLists.txt index 5246dd73b5..b91b48bba0 100644 --- a/src/sound/resid-fp/CMakeLists.txt +++ b/src/sound/resid-fp/CMakeLists.txt @@ -13,6 +13,8 @@ # Copyright 2020-2021 David Hrdlička. # +set(CMAKE_CXX_STANDARD 17) + add_library(resid-fp STATIC Dac.cpp EnvelopeGenerator.cpp ExternalFilter.cpp Filter.cpp Filter6581.cpp Filter8580.cpp FilterModelConfig.cpp FilterModelConfig6581.cpp FilterModelConfig8580.cpp diff --git a/src/sound/resid-fp/Dac.cpp b/src/sound/resid-fp/Dac.cpp index 0665da8177..5ae5429b69 100644 --- a/src/sound/resid-fp/Dac.cpp +++ b/src/sound/resid-fp/Dac.cpp @@ -1,7 +1,7 @@ /* * This file is part of libsidplayfp, a SID player engine. * - * Copyright 2011-2016 Leandro Nini + * Copyright 2011-2024 Leandro Nini * Copyright 2007-2010 Antti Lankila * Copyright 2004,2010 Dag Lem * @@ -22,9 +22,14 @@ #include "Dac.h" +#include "sidcxx11.h" + namespace reSIDfp { +constexpr double MOSFET_LEAKAGE_6581 = 0.0075; +constexpr double MOSFET_LEAKAGE_8580 = 0.0035; + Dac::Dac(unsigned int bits) : dac(new double[bits]), dacLength(bits) @@ -41,10 +46,8 @@ double Dac::getOutput(unsigned int input) const for (unsigned int i = 0; i < dacLength; i++) { - if ((input & (1 << i)) != 0) - { - dacValue += dac[i]; - } + const bool transistor_on = (input & (1 << i)) != 0; + dacValue += transistor_on ? dac[i] : dac[i] * leakage; } return dacValue; @@ -52,7 +55,7 @@ double Dac::getOutput(unsigned int input) const void Dac::kinkedDac(ChipModel chipModel) { - const double R_INFINITY = 1e6; + constexpr double R_INFINITY = 1e6; // Non-linearity parameter, 8580 DACs are perfectly linear const double _2R_div_R = chipModel == MOS6581 ? 2.20 : 2.00; @@ -60,6 +63,10 @@ void Dac::kinkedDac(ChipModel chipModel) // 6581 DACs are not terminated by a 2R resistor const bool term = chipModel == MOS8580; + leakage = chipModel == MOS6581 ? MOSFET_LEAKAGE_6581 : MOSFET_LEAKAGE_8580; + + double Vsum = 0.; + // Calculate voltage contribution by each individual bit in the R-2R ladder. for (unsigned int set_bit = 0; set_bit < dacLength; set_bit++) { @@ -102,18 +109,10 @@ void Dac::kinkedDac(ChipModel chipModel) } dac[set_bit] = Vn; + Vsum += Vn; } // Normalize to integerish behavior - double Vsum = 0.; - - for (unsigned int i = 0; i < dacLength; i++) - { - Vsum += dac[i]; - } - - Vsum /= 1 << dacLength; - for (unsigned int i = 0; i < dacLength; i++) { dac[i] /= Vsum; diff --git a/src/sound/resid-fp/Dac.h b/src/sound/resid-fp/Dac.h index 35bc0b2cab..757f12e4e1 100644 --- a/src/sound/resid-fp/Dac.h +++ b/src/sound/resid-fp/Dac.h @@ -75,6 +75,15 @@ namespace reSIDfp class Dac { private: + /** + * DAC leakage + * + * "Even in standard transistors a small amount of current leaks even when they are technically switched off." + * + * https://en.wikipedia.org/wiki/Subthreshold_conduction + */ + double leakage; + /// analog values double * const dac; diff --git a/src/sound/resid-fp/EnvelopeGenerator.cpp b/src/sound/resid-fp/EnvelopeGenerator.cpp index af636ac7f4..e7f5f4e8a5 100644 --- a/src/sound/resid-fp/EnvelopeGenerator.cpp +++ b/src/sound/resid-fp/EnvelopeGenerator.cpp @@ -79,7 +79,7 @@ void EnvelopeGenerator::reset() exponential_counter_period = 1; new_exponential_counter_period = 0; - state = RELEASE; + state = State::RELEASE; counter_enabled = true; rate = adsrtable[release]; } @@ -98,7 +98,7 @@ void EnvelopeGenerator::writeCONTROL_REG(unsigned char control) if (gate_next) { // Gate bit on: Start attack, decay, sustain. - next_state = ATTACK; + next_state = State::ATTACK; state_pipeline = 2; if (resetLfsr || (exponential_pipeline == 2)) @@ -113,7 +113,7 @@ void EnvelopeGenerator::writeCONTROL_REG(unsigned char control) else { // Gate bit off: Start release. - next_state = RELEASE; + next_state = State::RELEASE; state_pipeline = envelope_pipeline > 0 ? 3 : 2; } } @@ -124,11 +124,11 @@ void EnvelopeGenerator::writeATTACK_DECAY(unsigned char attack_decay) attack = (attack_decay >> 4) & 0x0f; decay = attack_decay & 0x0f; - if (state == ATTACK) + if (state == State::ATTACK) { rate = adsrtable[attack]; } - else if (state == DECAY_SUSTAIN) + else if (state == State::DECAY_SUSTAIN) { rate = adsrtable[decay]; } @@ -146,7 +146,7 @@ void EnvelopeGenerator::writeSUSTAIN_RELEASE(unsigned char sustain_release) release = sustain_release & 0x0f; - if (state == RELEASE) + if (state == State::RELEASE) { rate = adsrtable[release]; } diff --git a/src/sound/resid-fp/EnvelopeGenerator.h b/src/sound/resid-fp/EnvelopeGenerator.h index f2aab38743..554b814b1b 100644 --- a/src/sound/resid-fp/EnvelopeGenerator.h +++ b/src/sound/resid-fp/EnvelopeGenerator.h @@ -47,68 +47,68 @@ class EnvelopeGenerator * The envelope state machine's distinct states. In addition to this, * envelope has a hold mode, which freezes envelope counter to zero. */ - enum State + enum class State { ATTACK, DECAY_SUSTAIN, RELEASE }; private: /// XOR shift register for ADSR prescaling. - unsigned int lfsr; + unsigned int lfsr = 0x7fff; /// Comparison value (period) of the rate counter before next event. - unsigned int rate; + unsigned int rate = 0; /** * During release mode, the SID approximates envelope decay via piecewise * linear decay rate. */ - unsigned int exponential_counter; + unsigned int exponential_counter = 0; /** * Comparison value (period) of the exponential decay counter before next * decrement. */ - unsigned int exponential_counter_period; - unsigned int new_exponential_counter_period; + unsigned int exponential_counter_period = 1; + unsigned int new_exponential_counter_period = 0; - unsigned int state_pipeline; + unsigned int state_pipeline = 0; /// - unsigned int envelope_pipeline; + unsigned int envelope_pipeline = 0; - unsigned int exponential_pipeline; + unsigned int exponential_pipeline = 0; /// Current envelope state - State state; - State next_state; + State state = State::RELEASE; + State next_state = State::RELEASE; /// Whether counter is enabled. Only switching to ATTACK can release envelope. - bool counter_enabled; + bool counter_enabled = true; /// Gate bit - bool gate; + bool gate = false; /// - bool resetLfsr; + bool resetLfsr = false; /// The current digital value of envelope output. - unsigned char envelope_counter; + unsigned char envelope_counter = 0xaa; /// Attack register - unsigned char attack; + unsigned char attack = 0; /// Decay register - unsigned char decay; + unsigned char decay = 0; /// Sustain register - unsigned char sustain; + unsigned char sustain = 0; /// Release register - unsigned char release; + unsigned char release = 0; /// The ENV3 value, sampled at the first phase of the clock - unsigned char env3; + unsigned char env3 = 0; private: static const unsigned int adsrtable[16]; @@ -129,31 +129,6 @@ class EnvelopeGenerator */ unsigned int output() const { return envelope_counter; } - /** - * Constructor. - */ - EnvelopeGenerator() : - lfsr(0x7fff), - rate(0), - exponential_counter(0), - exponential_counter_period(1), - new_exponential_counter_period(0), - state_pipeline(0), - envelope_pipeline(0), - exponential_pipeline(0), - state(RELEASE), - next_state(RELEASE), - counter_enabled(true), - gate(false), - resetLfsr(false), - envelope_counter(0xaa), - attack(0), - decay(0), - sustain(0), - release(0), - env3(0) - {} - /** * SID reset. */ @@ -218,15 +193,15 @@ void EnvelopeGenerator::clock() { if (likely(counter_enabled)) { - if (state == ATTACK) + if (state == State::ATTACK) { if (++envelope_counter==0xff) { - next_state = DECAY_SUSTAIN; + next_state = State::DECAY_SUSTAIN; state_pipeline = 3; } } - else if ((state == DECAY_SUSTAIN) || (state == RELEASE)) + else if ((state == State::DECAY_SUSTAIN) || (state == State::RELEASE)) { if (--envelope_counter==0x00) { @@ -241,8 +216,8 @@ void EnvelopeGenerator::clock() { exponential_counter = 0; - if (((state == DECAY_SUSTAIN) && (envelope_counter != sustain)) - || (state == RELEASE)) + if (((state == State::DECAY_SUSTAIN) && (envelope_counter != sustain)) + || (state == State::RELEASE)) { // The envelope counter can flip from 0x00 to 0xff by changing state to // attack, then to release. The envelope counter will then continue @@ -257,7 +232,7 @@ void EnvelopeGenerator::clock() lfsr = 0x7fff; resetLfsr = false; - if (state == ATTACK) + if (state == State::ATTACK) { // The first envelope step in the attack state also resets the exponential // counter. This has been verified by sampling ENV3. @@ -344,7 +319,7 @@ void EnvelopeGenerator::state_change() switch (next_state) { - case ATTACK: + case State::ATTACK: if (state_pipeline == 1) { // The decay rate is "accidentally" enabled during first cycle of attack phase @@ -352,24 +327,24 @@ void EnvelopeGenerator::state_change() } else if (state_pipeline == 0) { - state = ATTACK; + state = State::ATTACK; // The attack rate is correctly enabled during second cycle of attack phase rate = adsrtable[attack]; counter_enabled = true; } break; - case DECAY_SUSTAIN: + case State::DECAY_SUSTAIN: if (state_pipeline == 0) { - state = DECAY_SUSTAIN; + state = State::DECAY_SUSTAIN; rate = adsrtable[decay]; } break; - case RELEASE: - if (((state == ATTACK) && (state_pipeline == 0)) - || ((state == DECAY_SUSTAIN) && (state_pipeline == 1))) + case State::RELEASE: + if (((state == State::ATTACK) && (state_pipeline == 0)) + || ((state == State::DECAY_SUSTAIN) && (state_pipeline == 1))) { - state = RELEASE; + state = State::RELEASE; rate = adsrtable[release]; } break; diff --git a/src/sound/resid-fp/ExternalFilter.cpp b/src/sound/resid-fp/ExternalFilter.cpp index eac790b31f..7f44715b58 100644 --- a/src/sound/resid-fp/ExternalFilter.cpp +++ b/src/sound/resid-fp/ExternalFilter.cpp @@ -38,9 +38,7 @@ inline double getRC(double res, double cap) return res * cap; } -ExternalFilter::ExternalFilter() : - w0lp_1_s7(0), - w0hp_1_s17(0) +ExternalFilter::ExternalFilter() { reset(); } diff --git a/src/sound/resid-fp/ExternalFilter.h b/src/sound/resid-fp/ExternalFilter.h index 760ee5c227..17e8b1649d 100644 --- a/src/sound/resid-fp/ExternalFilter.h +++ b/src/sound/resid-fp/ExternalFilter.h @@ -34,8 +34,6 @@ namespace reSIDfp * acts as a high-pass filter with a cutoff dependent on the attached audio * equipment impedance. Here we suppose an impedance of 10kOhm resulting * in a 3 dB attenuation at 1.6Hz. - * To operate properly the 6581 audio output needs a pull-down resistor - *(1KOhm recommended, not needed on 8580) * * ~~~ * 9/12V @@ -47,15 +45,18 @@ namespace reSIDfp * | | pF +-C----o-----C-----+ 10k * 470 | | * GND GND pF R 1K | amp - * * * | +----- + * * ** | +----- * * GND * ~~~ * * The STC networks are connected with a [BJT] based [common collector] * used as a voltage follower (featuring a 2SC1815 NPN transistor). - * * The C64c board additionally includes a [bootstrap] condenser to increase - * the input impedance of the common collector. + * + * * To operate properly the 6581 audio output needs a pull-down resistor + * (1KOhm recommended, not needed on 8580) + * ** The C64c board additionally includes a [bootstrap] condenser to increase + * the input impedance of the common collector. * * [BJT]: https://en.wikipedia.org/wiki/Bipolar_junction_transistor * [common collector]: https://en.wikipedia.org/wiki/Common_collector @@ -70,9 +71,9 @@ class ExternalFilter /// Highpass filter voltage int Vhp; - int w0lp_1_s7; + int w0lp_1_s7 = 0; - int w0hp_1_s17; + int w0hp_1_s17 = 0; public: /** @@ -80,7 +81,7 @@ class ExternalFilter * * @param input */ - int clock(unsigned short input); + int clock(int input); /** * Constructor. @@ -108,9 +109,9 @@ namespace reSIDfp { RESID_INLINE -int ExternalFilter::clock(unsigned short input) +int ExternalFilter::clock(int input) { - const int Vi = (static_cast(input)<<11) - (1 << (11+15)); + const int Vi = (input<<11) - (1 << (11+15)); const int dVlp = (w0lp_1_s7 * (Vi - Vlp) >> 7); const int dVhp = (w0hp_1_s17 * (Vlp - Vhp) >> 17); Vlp += dVlp; diff --git a/src/sound/resid-fp/Filter.cpp b/src/sound/resid-fp/Filter.cpp index 2a2dd24f78..6255c5729d 100644 --- a/src/sound/resid-fp/Filter.cpp +++ b/src/sound/resid-fp/Filter.cpp @@ -1,7 +1,7 @@ /* * This file is part of libsidplayfp, a SID player engine. * - * Copyright 2011-2013 Leandro Nini + * Copyright 2011-2024 Leandro Nini * Copyright 2007-2010 Antti Lankila * Copyright 2004 Dag Lem * @@ -20,43 +20,47 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define FILTER_CPP + #include "Filter.h" namespace reSIDfp { -void Filter::enable(bool enable) +void Filter::updateMixing() { - enabled = enable; + currentVolume = volume[vol]; - if (enabled) - { - writeRES_FILT(filt); - } - else - { - filt1 = filt2 = filt3 = filtE = false; - } -} + unsigned int Nsum = 0; + unsigned int Nmix = 0; -void Filter::reset() -{ - writeFC_LO(0); - writeFC_HI(0); - writeMODE_VOL(0); - writeRES_FILT(0); + (filt1 ? Nsum : Nmix)++; + (filt2 ? Nsum : Nmix)++; + + if (filt3) Nsum++; + else if (!voice3off) Nmix++; + + (filtE ? Nsum : Nmix)++; + + currentSummer = summer[Nsum]; + + if (lp) Nmix++; + if (bp) Nmix++; + if (hp) Nmix++; + + currentMixer = mixer[Nmix]; } void Filter::writeFC_LO(unsigned char fc_lo) { fc = (fc & 0x7f8) | (fc_lo & 0x007); - updatedCenterFrequency(); + updateCenterFrequency(); } void Filter::writeFC_HI(unsigned char fc_hi) { fc = (fc_hi << 3 & 0x7f8) | (fc & 0x007); - updatedCenterFrequency(); + updateCenterFrequency(); } void Filter::writeRES_FILT(unsigned char res_filt) @@ -73,7 +77,7 @@ void Filter::writeRES_FILT(unsigned char res_filt) filtE = (filt & 0x08) != 0; } - updatedMixing(); + updateMixing(); } void Filter::writeMODE_VOL(unsigned char mode_vol) @@ -84,7 +88,39 @@ void Filter::writeMODE_VOL(unsigned char mode_vol) hp = (mode_vol & 0x40) != 0; voice3off = (mode_vol & 0x80) != 0; - updatedMixing(); + updateMixing(); +} + +Filter::Filter(FilterModelConfig& fmc) : + mixer(fmc.getMixer()), + summer(fmc.getSummer()), + resonance(fmc.getResonance()), + volume(fmc.getVolume()), + fmc(fmc) +{ + input(0); +} + +void Filter::enable(bool enable) +{ + enabled = enable; + + if (enabled) + { + writeRES_FILT(filt); + } + else + { + filt1 = filt2 = filt3 = filtE = false; + } +} + +void Filter::reset() +{ + writeFC_LO(0); + writeFC_HI(0); + writeMODE_VOL(0); + writeRES_FILT(0); } } // namespace reSIDfp diff --git a/src/sound/resid-fp/Filter.h b/src/sound/resid-fp/Filter.h index 4b3473369e..6873d99068 100644 --- a/src/sound/resid-fp/Filter.h +++ b/src/sound/resid-fp/Filter.h @@ -1,7 +1,7 @@ /* * This file is part of libsidplayfp, a SID player engine. * - * Copyright 2011-2017 Leandro Nini + * Copyright 2011-2024 Leandro Nini * Copyright 2007-2010 Antti Lankila * Copyright 2004 Dag Lem * @@ -23,6 +23,10 @@ #ifndef FILTER_H #define FILTER_H +#include "FilterModelConfig.h" + +#include "siddefs-fp.h" + namespace reSIDfp { @@ -31,93 +35,97 @@ namespace reSIDfp */ class Filter { +private: + unsigned short** mixer; + unsigned short** summer; + unsigned short** resonance; + unsigned short** volume; + protected: - /// Current volume amplifier setting. - unsigned short* currentGain; + FilterModelConfig& fmc; /// Current filter/voice mixer setting. - unsigned short* currentMixer; + unsigned short* currentMixer = nullptr; /// Filter input summer setting. - unsigned short* currentSummer; + unsigned short* currentSummer = nullptr; /// Filter resonance value. - unsigned short* currentResonance; + unsigned short* currentResonance = nullptr; + + /// Current volume amplifier setting. + unsigned short* currentVolume = nullptr; /// Filter highpass state. - int Vhp; + int Vhp = 0; /// Filter bandpass state. - int Vbp; + int Vbp = 0; /// Filter lowpass state. - int Vlp; + int Vlp = 0; /// Filter external input. - int ve; + int Ve = 0; /// Filter cutoff frequency. - unsigned int fc; + unsigned int fc = 0; /// Routing to filter or outside filter - bool filt1, filt2, filt3, filtE; + //@{ + bool filt1 = false; + bool filt2 = false; + bool filt3 = false; + bool filtE = false; + //@} /// Switch voice 3 off. - bool voice3off; + bool voice3off = false; /// Highpass, bandpass, and lowpass filter modes. - bool hp, bp, lp; + //@{ + bool hp = false; + bool bp = false; + bool lp = false; + //@} +private: /// Current volume. - unsigned char vol; + unsigned char vol = 0; -private: /// Filter enabled. - bool enabled; + bool enabled = true; /// Selects which inputs to route through filter. - unsigned char filt; + unsigned char filt = 0; protected: /** - * Set filter cutoff frequency. + * Update filter cutoff frequency. */ - virtual void updatedCenterFrequency() = 0; + virtual void updateCenterFrequency() = 0; /** - * Set filter resonance. + * Update filter resonance. + * + * @param res the new resonance value */ - virtual void updateResonance(unsigned char res) = 0; + void updateResonance(unsigned char res) { currentResonance = resonance[res]; } /** * Mixing configuration modified (offsets change) */ - virtual void updatedMixing() = 0; + void updateMixing(); + + /** + * Get the filter cutoff register value + */ + unsigned int getFC() const { return fc; } public: - Filter() : - currentGain(nullptr), - currentMixer(nullptr), - currentSummer(nullptr), - currentResonance(nullptr), - Vhp(0), - Vbp(0), - Vlp(0), - ve(0), - fc(0), - filt1(false), - filt2(false), - filt3(false), - filtE(false), - voice3off(false), - hp(false), - bp(false), - lp(false), - vol(0), - enabled(true), - filt(0) {} - - virtual ~Filter() {} + Filter(FilterModelConfig& fmc); + + virtual ~Filter() = default; /** * SID clocking - 1 cycle @@ -169,7 +177,14 @@ class Filter */ void writeMODE_VOL(unsigned char mode_vol); - virtual void input(int input) = 0; + /** + * Apply a signal to EXT-IN + * + * @param input a signed 16 bit sample + */ + void input(short input) { Ve = fmc.getNormalizedVoice(input/32768.f, 0); } + + inline int getNormalizedVoice(float value, unsigned int env) const { return fmc.getNormalizedVoice(value, env); } }; } // namespace reSIDfp diff --git a/src/sound/resid-fp/Filter6581.cpp b/src/sound/resid-fp/Filter6581.cpp index c064a88016..b761c22ea3 100644 --- a/src/sound/resid-fp/Filter6581.cpp +++ b/src/sound/resid-fp/Filter6581.cpp @@ -1,7 +1,7 @@ /* * This file is part of libsidplayfp, a SID player engine. * - * Copyright 2011-2015 Leandro Nini + * Copyright 2011-2024 Leandro Nini * Copyright 2007-2010 Antti Lankila * Copyright 2004,2010 Dag Lem * @@ -20,8 +20,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#define FILTER6581_CPP - #include "Filter6581.h" #include "Integrator6581.h" @@ -29,47 +27,60 @@ namespace reSIDfp { -Filter6581::~Filter6581() +unsigned short Filter6581::clock(int voice1, int voice2, int voice3) { - delete [] f0_dac; + const int V1 = voice1; + const int V2 = voice2; + // Voice 3 is silenced by voice3off if it is not routed through the filter. + const int V3 = (filt3 || !voice3off) ? voice3 : 0; + + int Vsum = 0; + int Vmix = 0; + + (filt1 ? Vsum : Vmix) += V1; + (filt2 ? Vsum : Vmix) += V2; + (filt3 ? Vsum : Vmix) += V3; + (filtE ? Vsum : Vmix) += Ve; + + Vhp = currentSummer[currentResonance[Vbp] + Vlp + Vsum]; + Vbp = hpIntegrator.solve(Vhp); + Vlp = bpIntegrator.solve(Vbp); + + int Vfilt = 0; + if (lp) Vfilt += Vlp; + if (bp) Vfilt += Vbp; + if (hp) Vfilt += Vhp; + + // The filter input resistors are slightly bigger than the voice ones + // Scale the values accordingly + constexpr int filterGain = static_cast(0.93 * (1 << 12)); + Vfilt = (Vfilt * filterGain) >> 12; + + return currentVolume[currentMixer[Vmix + Vfilt]]; } -void Filter6581::updatedCenterFrequency() +Filter6581::~Filter6581() { - const unsigned short Vw = f0_dac[fc]; - hpIntegrator->setVw(Vw); - bpIntegrator->setVw(Vw); + delete [] f0_dac; } -void Filter6581::updatedMixing() +void Filter6581::updateCenterFrequency() { - currentGain = gain_vol[vol]; - - unsigned int ni = 0; - unsigned int no = 0; - - (filt1 ? ni : no)++; - (filt2 ? ni : no)++; - - if (filt3) ni++; - else if (!voice3off) no++; - - (filtE ? ni : no)++; - - currentSummer = summer[ni]; - - if (lp) no++; - if (bp) no++; - if (hp) no++; - - currentMixer = mixer[no]; + const unsigned short Vw = f0_dac[getFC()]; + hpIntegrator.setVw(Vw); + bpIntegrator.setVw(Vw); } void Filter6581::setFilterCurve(double curvePosition) { delete [] f0_dac; f0_dac = FilterModelConfig6581::getInstance()->getDAC(curvePosition); - updatedCenterFrequency(); + updateCenterFrequency(); +} + +void Filter6581::setFilterRange(double adjustment) +{ + FilterModelConfig6581::getInstance()->setFilterRange(adjustment); } } // namespace reSIDfp diff --git a/src/sound/resid-fp/Filter6581.h b/src/sound/resid-fp/Filter6581.h index 7fca331ab4..27b97b9915 100644 --- a/src/sound/resid-fp/Filter6581.h +++ b/src/sound/resid-fp/Filter6581.h @@ -1,7 +1,7 @@ /* * This file is part of libsidplayfp, a SID player engine. * - * Copyright 2011-2022 Leandro Nini + * Copyright 2011-2024 Leandro Nini * Copyright 2007-2010 Antti Lankila * Copyright 2004,2010 Dag Lem * @@ -23,12 +23,9 @@ #ifndef FILTER6581_H #define FILTER6581_H -#include "siddefs-fp.h" - -#include - #include "Filter.h" #include "FilterModelConfig6581.h" +#include "Integrator6581.h" #include "sidcxx11.h" @@ -108,7 +105,7 @@ class Integrator6581; * | | | v1 | | | | * D0 | | | \ ---R8--+ | | +---------------------------+ * | | | | | | | - * R6 R6 R6 R6 R6 R6 R6 + * R6 R6 R6 R6 R6* R6* R6* * | | | | $18 | | | $18 * | \ | | D7: 1=open \ \ \ D6 - D4: 0=open * | | | | | | | @@ -143,6 +140,7 @@ class Integrator6581; * * R2 ~ 2.0*R1 * R6 ~ 6.0*R1 + * R6* ~ 1.07*R6 * R8 ~ 8.0*R1 * R24 ~ 24.0*R1 * @@ -322,104 +320,49 @@ class Integrator6581; class Filter6581 final : public Filter { private: - const unsigned short* f0_dac; - - unsigned short** mixer; - unsigned short** summer; - unsigned short** gain_res; - unsigned short** gain_vol; - - const int voiceScaleS11; - const int voiceDC; - /// VCR + associated capacitor connected to highpass output. - std::unique_ptr const hpIntegrator; + Integrator6581 hpIntegrator; /// VCR + associated capacitor connected to bandpass output. - std::unique_ptr const bpIntegrator; + Integrator6581 bpIntegrator; + + const unsigned short* f0_dac; protected: /** * Set filter cutoff frequency. */ - void updatedCenterFrequency() override; - - /** - * Set filter resonance. - * - * In the MOS 6581, 1/Q is controlled linearly by res. - */ - void updateResonance(unsigned char res) override { currentResonance = gain_res[res]; } - - void updatedMixing() override; + void updateCenterFrequency() override; public: Filter6581() : - f0_dac(FilterModelConfig6581::getInstance()->getDAC(0.5)), - mixer(FilterModelConfig6581::getInstance()->getMixer()), - summer(FilterModelConfig6581::getInstance()->getSummer()), - gain_res(FilterModelConfig6581::getInstance()->getGainRes()), - gain_vol(FilterModelConfig6581::getInstance()->getGainVol()), - voiceScaleS11(FilterModelConfig6581::getInstance()->getVoiceScaleS11()), - voiceDC(FilterModelConfig6581::getInstance()->getNormalizedVoiceDC()), - hpIntegrator(FilterModelConfig6581::getInstance()->buildIntegrator()), - bpIntegrator(FilterModelConfig6581::getInstance()->buildIntegrator()) - { - input(0); - } + Filter(*FilterModelConfig6581::getInstance()), + hpIntegrator(*FilterModelConfig6581::getInstance()), + bpIntegrator(*FilterModelConfig6581::getInstance()), + f0_dac(FilterModelConfig6581::getInstance()->getDAC(0.5)) + {} - ~Filter6581(); + ~Filter6581() override; - unsigned short clock(int voice1, int voice2, int voice3) override; - - void input(int sample) override { ve = (sample * voiceScaleS11 * 3 >> 11) + mixer[0][0]; } + unsigned short clock(int v1, int v2, int v3) override; /** * Set filter curve type based on single parameter. * - * @param curvePosition 0 .. 1, where 0 sets center frequency high ("light") and 1 sets it low ("dark"), default is 0.5 + * @param curvePosition 0 .. 1, where 0 sets center frequency high ("bright") and 1 sets it low ("dark"). + * Default is 0.5 */ void setFilterCurve(double curvePosition); -}; - -} // namespace reSIDfp -#if RESID_INLINING || defined(FILTER6581_CPP) - -#include "Integrator6581.h" - -namespace reSIDfp -{ - -RESID_INLINE -unsigned short Filter6581::clock(int voice1, int voice2, int voice3) -{ - voice1 = (voice1 * voiceScaleS11 >> 15) + voiceDC; - voice2 = (voice2 * voiceScaleS11 >> 15) + voiceDC; - // Voice 3 is silenced by voice3off if it is not routed through the filter. - voice3 = (filt3 || !voice3off) ? (voice3 * voiceScaleS11 >> 15) + voiceDC : 0; - - int Vi = 0; - int Vo = 0; - - (filt1 ? Vi : Vo) += voice1; - (filt2 ? Vi : Vo) += voice2; - (filt3 ? Vi : Vo) += voice3; - (filtE ? Vi : Vo) += ve; - - Vhp = currentSummer[currentResonance[Vbp] + Vlp + Vi]; - Vbp = hpIntegrator->solve(Vhp); - Vlp = bpIntegrator->solve(Vbp); - - if (lp) Vo += Vlp; - if (bp) Vo += Vbp; - if (hp) Vo += Vhp; - - return currentGain[currentMixer[Vo]]; -} + /** + * Set filter offset and range based on single parameter. + * + * @param adjustment 0 .. 1, where 0 sets center frequency low ("dark"), 1 sets it high ("bright"). + * This also affects the range. Default is 0.5 + */ + void setFilterRange(double adjustment); +}; } // namespace reSIDfp #endif - -#endif diff --git a/src/sound/resid-fp/Filter8580.cpp b/src/sound/resid-fp/Filter8580.cpp index a70285a8a0..c54e2b741a 100644 --- a/src/sound/resid-fp/Filter8580.cpp +++ b/src/sound/resid-fp/Filter8580.cpp @@ -1,7 +1,7 @@ /* * This file is part of libsidplayfp, a SID player engine. * - * Copyright 2011-2019 Leandro Nini + * Copyright 2011-2024 Leandro Nini * Copyright 2007-2010 Antti Lankila * Copyright 2004,2010 Dag Lem * @@ -20,8 +20,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#define FILTER8580_CPP - #include "Filter8580.h" #include "Integrator8580.h" @@ -29,6 +27,32 @@ namespace reSIDfp { +unsigned short Filter8580::clock(int voice1, int voice2, int voice3) +{ + const int V1 = voice1; + const int V2 = voice2; + // Voice 3 is silenced by voice3off if it is not routed through the filter. + const int V3 = (filt3 || !voice3off) ? voice3 : 0; + + int Vsum = 0; + int Vmix = 0; + + (filt1 ? Vsum : Vmix) += V1; + (filt2 ? Vsum : Vmix) += V2; + (filt3 ? Vsum : Vmix) += V3; + (filtE ? Vsum : Vmix) += Ve; + + Vhp = currentSummer[currentResonance[Vbp] + Vlp + Vsum]; + Vbp = hpIntegrator.solve(Vhp); + Vlp = bpIntegrator.solve(Vbp); + + if (lp) Vmix += Vlp; + if (bp) Vmix += Vbp; + if (hp) Vmix += Vhp; + + return currentVolume[currentMixer[Vmix]]; +} + /** * W/L ratio of frequency DAC bit 0, * other bit are proportional. @@ -37,18 +61,18 @@ namespace reSIDfp */ const double DAC_WL0 = 0.00615; -Filter8580::~Filter8580() {} +Filter8580::~Filter8580() = default; -void Filter8580::updatedCenterFrequency() +void Filter8580::updateCenterFrequency() { double wl; double dacWL = DAC_WL0; - if (fc) + if (getFC()) { wl = 0.; for (unsigned int i = 0; i < 11; i++) { - if (fc & (1 << i)) + if (getFC() & (1 << i)) { wl += dacWL; } @@ -60,32 +84,8 @@ void Filter8580::updatedCenterFrequency() wl = dacWL/2.; } - hpIntegrator->setFc(wl); - bpIntegrator->setFc(wl); -} - -void Filter8580::updatedMixing() -{ - currentGain = gain_vol[vol]; - - unsigned int ni = 0; - unsigned int no = 0; - - (filt1 ? ni : no)++; - (filt2 ? ni : no)++; - - if (filt3) ni++; - else if (!voice3off) no++; - - (filtE ? ni : no)++; - - currentSummer = summer[ni]; - - if (lp) no++; - if (bp) no++; - if (hp) no++; - - currentMixer = mixer[no]; + hpIntegrator.setFc(wl); + bpIntegrator.setFc(wl); } void Filter8580::setFilterCurve(double curvePosition) @@ -94,8 +94,8 @@ void Filter8580::setFilterCurve(double curvePosition) // 1.2 <= cp <= 1.8 cp = 1.8 - curvePosition * 3./5.; - hpIntegrator->setV(cp); - bpIntegrator->setV(cp); + hpIntegrator.setV(cp); + bpIntegrator.setV(cp); } } // namespace reSIDfp diff --git a/src/sound/resid-fp/Filter8580.h b/src/sound/resid-fp/Filter8580.h index 2166ec0da6..59dbfceeec 100644 --- a/src/sound/resid-fp/Filter8580.h +++ b/src/sound/resid-fp/Filter8580.h @@ -1,7 +1,7 @@ /* * This file is part of libsidplayfp, a SID player engine. * - * Copyright 2011-2022 Leandro Nini + * Copyright 2011-2024 Leandro Nini * Copyright 2007-2010 Antti Lankila * Copyright 2004,2010 Dag Lem * @@ -23,10 +23,6 @@ #ifndef FILTER8580_H #define FILTER8580_H -#include "siddefs-fp.h" - -#include - #include "Filter.h" #include "FilterModelConfig8580.h" #include "Integrator8580.h" @@ -281,58 +277,32 @@ class Integrator8580; class Filter8580 final : public Filter { private: - unsigned short** mixer; - unsigned short** summer; - unsigned short** gain_res; - unsigned short** gain_vol; - - const int voiceScaleS11; - const int voiceDC; - - double cp; - /// VCR + associated capacitor connected to highpass output. - std::unique_ptr const hpIntegrator; + Integrator8580 hpIntegrator; /// VCR + associated capacitor connected to bandpass output. - std::unique_ptr const bpIntegrator; + Integrator8580 bpIntegrator; + + double cp; protected: /** * Set filter cutoff frequency. */ - void updatedCenterFrequency() override; - - /** - * Set filter resonance. - * - * @param res the new resonance value - */ - void updateResonance(unsigned char res) override { currentResonance = gain_res[res]; } - - void updatedMixing() override; + void updateCenterFrequency() override; public: Filter8580() : - mixer(FilterModelConfig8580::getInstance()->getMixer()), - summer(FilterModelConfig8580::getInstance()->getSummer()), - gain_res(FilterModelConfig8580::getInstance()->getGainRes()), - gain_vol(FilterModelConfig8580::getInstance()->getGainVol()), - voiceScaleS11(FilterModelConfig8580::getInstance()->getVoiceScaleS11()), - voiceDC(FilterModelConfig8580::getInstance()->getNormalizedVoiceDC()), - cp(0.5), - hpIntegrator(FilterModelConfig8580::getInstance()->buildIntegrator()), - bpIntegrator(FilterModelConfig8580::getInstance()->buildIntegrator()) + Filter(*FilterModelConfig8580::getInstance()), + hpIntegrator(*FilterModelConfig8580::getInstance()), + bpIntegrator(*FilterModelConfig8580::getInstance()) { - setFilterCurve(cp); - input(0); + setFilterCurve(0.5); } - ~Filter8580(); - - unsigned short clock(int voice1, int voice2, int voice3) override; + ~Filter8580() override; - void input(int sample) override { ve = (sample * voiceScaleS11 * 3 >> 11) + mixer[0][0]; } + unsigned short clock(int v1, int v2, int v3) override; /** * Set filter curve type based on single parameter. @@ -344,40 +314,4 @@ class Filter8580 final : public Filter } // namespace reSIDfp -#if RESID_INLINING || defined(FILTER8580_CPP) - -namespace reSIDfp -{ - -RESID_INLINE -unsigned short Filter8580::clock(int voice1, int voice2, int voice3) -{ - voice1 = (voice1 * voiceScaleS11 >> 15) + voiceDC; - voice2 = (voice2 * voiceScaleS11 >> 15) + voiceDC; - // Voice 3 is silenced by voice3off if it is not routed through the filter. - voice3 = (filt3 || !voice3off) ? (voice3 * voiceScaleS11 >> 15) + voiceDC : 0; - - int Vi = 0; - int Vo = 0; - - (filt1 ? Vi : Vo) += voice1; - (filt2 ? Vi : Vo) += voice2; - (filt3 ? Vi : Vo) += voice3; - (filtE ? Vi : Vo) += ve; - - Vhp = currentSummer[currentResonance[Vbp] + Vlp + Vi]; - Vbp = hpIntegrator->solve(Vhp); - Vlp = bpIntegrator->solve(Vbp); - - if (lp) Vo += Vlp; - if (bp) Vo += Vbp; - if (hp) Vo += Vhp; - - return currentGain[currentMixer[Vo]]; -} - -} // namespace reSIDfp - -#endif - #endif diff --git a/src/sound/resid-fp/FilterModelConfig.cpp b/src/sound/resid-fp/FilterModelConfig.cpp index cd4b204000..2ab459164c 100644 --- a/src/sound/resid-fp/FilterModelConfig.cpp +++ b/src/sound/resid-fp/FilterModelConfig.cpp @@ -1,7 +1,7 @@ /* * This file is part of libsidplayfp, a SID player engine. * - * Copyright 2011-2022 Leandro Nini + * Copyright 2011-2024 Leandro Nini * Copyright 2007-2010 Antti Lankila * Copyright 2004,2010 Dag Lem * @@ -29,7 +29,6 @@ namespace reSIDfp FilterModelConfig::FilterModelConfig( double vvr, - double vdv, double c, double vdd, double vth, @@ -37,21 +36,19 @@ FilterModelConfig::FilterModelConfig( const Spline::Point *opamp_voltage, int opamp_size ) : - voice_voltage_range(vvr), - voice_DC_voltage(vdv), C(c), Vdd(vdd), Vth(vth), - Ut(26.0e-3), - uCox(ucox), Vddt(Vdd - Vth), vmin(opamp_voltage[0].x), vmax(std::max(Vddt, opamp_voltage[0].y)), denorm(vmax - vmin), norm(1.0 / denorm), N16(norm * ((1 << 16) - 1)), - currFactorCoeff(denorm * (uCox / 2. * 1.0e-6 / C)) + voice_voltage_range(vvr) { + setUCox(ucox); + // Convert op-amp voltage transfer to 16 bit values. std::vector scaled_voltage(opamp_size); @@ -79,4 +76,29 @@ FilterModelConfig::FilterModelConfig( } } +FilterModelConfig::~FilterModelConfig() +{ + for (int i = 0; i < 8; i++) + { + delete [] mixer[i]; + } + + for (int i = 0; i < 5; i++) + { + delete [] summer[i]; + } + + for (int i = 0; i < 16; i++) + { + delete [] volume[i]; + delete [] resonance[i]; + } +} + +void FilterModelConfig::setUCox(double new_uCox) +{ + uCox = new_uCox; + currFactorCoeff = denorm * (uCox / 2. * 1.0e-6 / C); +} + } // namespace reSIDfp diff --git a/src/sound/resid-fp/FilterModelConfig.h b/src/sound/resid-fp/FilterModelConfig.h index 9e557d363a..1d37a8bf3c 100644 --- a/src/sound/resid-fp/FilterModelConfig.h +++ b/src/sound/resid-fp/FilterModelConfig.h @@ -1,7 +1,7 @@ /* * This file is part of libsidplayfp, a SID player engine. * - * Copyright 2011-2023 Leandro Nini + * Copyright 2011-2024 Leandro Nini * Copyright 2007-2010 Antti Lankila * Copyright 2004,2010 Dag Lem * @@ -24,8 +24,10 @@ #define FILTERMODELCONFIG_H #include +#include #include +#include "OpAmp.h" #include "Spline.h" #include "sidcxx11.h" @@ -35,20 +37,46 @@ namespace reSIDfp class FilterModelConfig { -protected: - const double voice_voltage_range; - const double voice_DC_voltage; +private: + /* + * Hack to add quick dither when converting values from float to int + * and avoid quantization noise. + * Hopefully this can be removed the day we move all the analog part + * processing to floats. + * + * Not sure about the effect of using such small buffer of numbers + * since the random sequence repeats every 1024 values but for + * now it seems to do the job. + */ + class Randomnoise + { + private: + double buffer[1024]; + mutable int index = 0; + public: + Randomnoise() + { + std::uniform_real_distribution unif(0., 1.); + std::default_random_engine re; + for (int i=0; i<1024; i++) + buffer[i] = unif(re); + } + double getNoise() const { index = (index + 1) & 0x3ff; return buffer[index]; } + }; +protected: /// Capacitor value. const double C; /// Transistor parameters. //@{ - const double Vdd; + /// Thermal voltage: Ut = kT/q = 8.61734315e-5*T ~ 26mV + static constexpr double Ut = 26.0e-3; + + const double Vdd; ///< Positive supply voltage const double Vth; ///< Threshold voltage - const double Ut; ///< Thermal voltage: Ut = kT/q = 8.61734315e-5*T ~ 26mV - const double uCox; ///< Transconductance coefficient: u*Cox const double Vddt; ///< Vdd - Vth + double uCox; ///< Transconductance coefficient: u*Cox //@} // Derived stuff @@ -58,38 +86,46 @@ class FilterModelConfig /// Fixed point scaling for 16 bit op-amp output. const double N16; + const double voice_voltage_range; + /// Current factor coefficient for op-amp integrators. - const double currFactorCoeff; + double currFactorCoeff; /// Lookup tables for gain and summer op-amps in output stage / filter. //@{ unsigned short* mixer[8]; //-V730_NOINIT this is initialized in the derived class constructor unsigned short* summer[5]; //-V730_NOINIT this is initialized in the derived class constructor - unsigned short* gain_vol[16]; //-V730_NOINIT this is initialized in the derived class constructor - unsigned short* gain_res[16]; //-V730_NOINIT this is initialized in the derived class constructor + unsigned short* volume[16]; //-V730_NOINIT this is initialized in the derived class constructor + unsigned short* resonance[16]; //-V730_NOINIT this is initialized in the derived class constructor //@} /// Reverse op-amp transfer function. unsigned short opamp_rev[1 << 16]; //-V730_NOINIT this is initialized in the derived class constructor private: - FilterModelConfig (const FilterModelConfig&) DELETE; - FilterModelConfig& operator= (const FilterModelConfig&) DELETE; + Randomnoise rnd; + +private: + FilterModelConfig(const FilterModelConfig&) = delete; + FilterModelConfig& operator= (const FilterModelConfig&) = delete; + + inline double getVoiceVoltage(float value, unsigned int env) const + { + return value * voice_voltage_range + getVoiceDC(env); + } protected: /** * @param vvr voice voltage range - * @param vdv voice DC voltage * @param c capacitor value - * @param vdd Vdd + * @param vdd Vdd supply voltage * @param vth threshold voltage * @param ucox u*Cox - * @param ominv opamp min voltage - * @param omaxv opamp max voltage + * @param opamp_voltage opamp voltage array + * @param opamp_size opamp voltage array size */ FilterModelConfig( double vvr, - double vdv, double c, double vdd, double vth, @@ -98,52 +134,139 @@ class FilterModelConfig int opamp_size ); - ~FilterModelConfig() + ~FilterModelConfig(); + + void setUCox(double new_uCox); + + virtual double getVoiceDC(unsigned int env) const = 0; + + /** + * The filter summer operates at n ~ 1, and has 5 fundamentally different + * input configurations (2 - 6 input "resistors"). + * + * Note that all "on" transistors are modeled as one. This is not + * entirely accurate, since the input for each transistor is different, + * and transistors are not linear components. However modeling all + * transistors separately would be extremely costly. + */ + inline void buildSummerTable(const OpAmp& opampModel) { - for (int i = 0; i < 8; i++) - { - delete [] mixer[i]; - } + const double r_N16 = 1. / N16; for (int i = 0; i < 5; i++) { - delete [] summer[i]; + const int idiv = 2 + i; // 2 - 6 input "resistors". + const int size = idiv << 16; + const double n = idiv; + const double r_idiv = 1. / idiv; + opampModel.reset(); + summer[i] = new unsigned short[size]; + + for (int vi = 0; vi < size; vi++) + { + const double vin = vmin + vi * r_N16 * r_idiv; /* vmin .. vmax */ + summer[i][vi] = getNormalizedValue(opampModel.solve(n, vin)); + } } + } + + /** + * The audio mixer operates at n ~ 8/6 (6581) or 8/5 (8580), + * and has 8 fundamentally different input configurations + * (0 - 7 input "resistors"). + * + * All "on", transistors are modeled as one - see comments above for + * the filter summer. + */ + inline void buildMixerTable(const OpAmp& opampModel, double nRatio) + { + const double r_N16 = 1. / N16; - for (int i = 0; i < 16; i++) + for (int i = 0; i < 8; i++) { - delete [] gain_vol[i]; - delete [] gain_res[i]; + const int idiv = (i == 0) ? 1 : i; + const int size = (i == 0) ? 1 : i << 16; + const double n = i * nRatio; + const double r_idiv = 1. / idiv; + opampModel.reset(); + mixer[i] = new unsigned short[size]; + + for (int vi = 0; vi < size; vi++) + { + const double vin = vmin + vi * r_N16 * r_idiv; /* vmin .. vmax */ + mixer[i][vi] = getNormalizedValue(opampModel.solve(n, vin)); + } } } -public: - unsigned short** getGainVol() { return gain_vol; } - unsigned short** getGainRes() { return gain_res; } - unsigned short** getSummer() { return summer; } - unsigned short** getMixer() { return mixer; } - /** - * The digital range of one voice is 20 bits; create a scaling term - * for multiplication which fits in 11 bits. + * 4 bit "resistor" ladders in the audio output gain + * necessitate 16 gain tables. + * From die photographs of the volume "resistor" ladders + * it follows that gain ~ vol/12 (6581) or vol/16 (8580) + * (assuming ideal op-amps and ideal "resistors"). */ - int getVoiceScaleS11() const { return static_cast((norm * ((1 << 11) - 1)) * voice_voltage_range); } + inline void buildVolumeTable(const OpAmp& opampModel, double nDivisor) + { + const double r_N16 = 1. / N16; + + for (int n8 = 0; n8 < 16; n8++) + { + const int size = 1 << 16; + const double n = n8 / nDivisor; + opampModel.reset(); + volume[n8] = new unsigned short[size]; + + for (int vi = 0; vi < size; vi++) + { + const double vin = vmin + vi * r_N16; /* vmin .. vmax */ + volume[n8][vi] = getNormalizedValue(opampModel.solve(n, vin)); + } + } + } /** - * The "zero" output level of the voices. + * 4 bit "resistor" ladders in the bandpass resonance gain + * necessitate 16 gain tables. + * From die photographs of the bandpass "resistor" ladders + * it follows that 1/Q ~ ~res/8 (6581) or 2^((4 - res)/8) (8580) + * (assuming ideal op-amps and ideal "resistors"). */ - int getNormalizedVoiceDC() const { return static_cast(N16 * (voice_DC_voltage - vmin)); } + inline void buildResonanceTable(const OpAmp& opampModel, const double resonance_n[16]) + { + const double r_N16 = 1. / N16; + + for (int n8 = 0; n8 < 16; n8++) + { + const int size = 1 << 16; + opampModel.reset(); + resonance[n8] = new unsigned short[size]; + + for (int vi = 0; vi < size; vi++) + { + const double vin = vmin + vi * r_N16; /* vmin .. vmax */ + resonance[n8][vi] = getNormalizedValue(opampModel.solve(resonance_n[n8], vin)); + } + } + } + +public: + unsigned short** getVolume() { return volume; } + unsigned short** getResonance() { return resonance; } + unsigned short** getSummer() { return summer; } + unsigned short** getMixer() { return mixer; } inline unsigned short getOpampRev(int i) const { return opamp_rev[i]; } inline double getVddt() const { return Vddt; } inline double getVth() const { return Vth; } // helper functions + inline unsigned short getNormalizedValue(double value) const { const double tmp = N16 * (value - vmin); - assert(tmp > -0.5 && tmp < 65535.5); - return static_cast(tmp + 0.5); + assert(tmp >= 0. && tmp <= 65535.); + return static_cast(tmp + rnd.getNoise()); } inline unsigned short getNormalizedCurrentFactor(double wl) const @@ -153,11 +276,17 @@ class FilterModelConfig return static_cast(tmp + 0.5); } - inline unsigned short getNVmin() const { + inline unsigned short getNVmin() const + { const double tmp = N16 * vmin; assert(tmp > -0.5 && tmp < 65535.5); return static_cast(tmp + 0.5); } + + inline int getNormalizedVoice(float value, unsigned int env) const + { + return static_cast(getNormalizedValue(getVoiceVoltage(value, env))); + } }; } // namespace reSIDfp diff --git a/src/sound/resid-fp/FilterModelConfig6581.cpp b/src/sound/resid-fp/FilterModelConfig6581.cpp index 143b9e91c1..fcbf32a46e 100644 --- a/src/sound/resid-fp/FilterModelConfig6581.cpp +++ b/src/sound/resid-fp/FilterModelConfig6581.cpp @@ -1,7 +1,7 @@ /* * This file is part of libsidplayfp, a SID player engine. * - * Copyright 2011-2023 Leandro Nini + * Copyright 2011-2024 Leandro Nini * Copyright 2007-2010 Antti Lankila * Copyright 2010 Dag Lem * @@ -22,28 +22,20 @@ #include "FilterModelConfig6581.h" -#include - #include "Integrator6581.h" #include "OpAmp.h" -namespace reSIDfp -{ +#include "sidcxx11.h" -#ifndef HAVE_CXX11 -/** - * Compute log(1+x) without losing precision for small values of x - * - * @note when compiling with -ffastm-math the compiler will - * optimize the expression away leaving a plain log(1. + x) - */ -inline double log1p(double x) +#include +#include +#include +#include + +namespace reSIDfp { - return log(1. + x) - (((1. + x) - 1.) - x) / (1. + x); -} -#endif -const unsigned int OPAMP_SIZE = 33; +constexpr unsigned int OPAMP_SIZE = 33; /** * This is the SID 6581 op-amp voltage transfer function, measured on @@ -51,7 +43,7 @@ const unsigned int OPAMP_SIZE = 33; * All measured chips have op-amps with output voltages (and thus input * voltages) within the range of 0.81V - 10.31V. */ -const Spline::Point opamp_voltage[OPAMP_SIZE] = +constexpr Spline::Point opamp_voltage[OPAMP_SIZE] = { { 0.81, 10.31 }, // Approximate start of actual range { 2.40, 10.31 }, @@ -90,8 +82,12 @@ const Spline::Point opamp_voltage[OPAMP_SIZE] = std::unique_ptr FilterModelConfig6581::instance(nullptr); +std::mutex Instance6581_Lock; + FilterModelConfig6581* FilterModelConfig6581::getInstance() { + std::lock_guard lock(Instance6581_Lock); + if (!instance.get()) { instance.reset(new FilterModelConfig6581()); @@ -100,14 +96,32 @@ FilterModelConfig6581* FilterModelConfig6581::getInstance() return instance.get(); } +void FilterModelConfig6581::setFilterRange(double adjustment) +{ + // clamp into allowed range +#ifdef HAVE_CXX17 + adjustment = std::clamp(adjustment, 0.0, 1.0); +#else + adjustment = std::max(std::min(adjustment, 1.0), 0.); +#endif + + // Get the new uCox value, in the range [1,40] + const double new_uCox = (1. + 39. * adjustment) * 1e-6; + + // Ignore small changes + if (std::abs(uCox - new_uCox) < 1e-12) + return; + + setUCox(new_uCox); +} + FilterModelConfig6581::FilterModelConfig6581() : FilterModelConfig( - 1.5, // voice voltage range - 5.075, // voice DC voltage - 470e-12, // capacitor value - 12.18, // Vdd - 1.31, // Vth - 20e-6, // uCox + 1.5, // voice voltage range FIXME should theoretically be ~3,571V + 470e-12, // capacitor value + 12. * VOLTAGE_SKEW, // Vdd + 1.31, // Vth + 20e-6, // uCox opamp_voltage, OPAMP_SIZE ), @@ -119,190 +133,144 @@ FilterModelConfig6581::FilterModelConfig6581() : { dac.kinkedDac(MOS6581); - // Create lookup tables for gains / summers. - -#ifndef _OPENMP - OpAmp opampModel( - std::vector( - std::begin(opamp_voltage), - std::end(opamp_voltage)), - Vddt, - vmin, - vmax); -#endif - -// #pragma omp parallel sections { -// #pragma omp section + Dac envDac(8); + envDac.kinkedDac(MOS6581); + for(int i=0; i<256; i++) { -#ifdef _OPENMP - OpAmp opampModel( - std::vector( - std::begin(opamp_voltage), - std::end(opamp_voltage)), - Vddt, - vmin, - vmax); -#endif - // The filter summer operates at n ~ 1, and has 5 fundamentally different - // input configurations (2 - 6 input "resistors"). - // - // Note that all "on" transistors are modeled as one. This is not - // entirely accurate, since the input for each transistor is different, - // and transistors are not linear components. However modeling all - // transistors separately would be extremely costly. - for (int i = 0; i < 5; i++) - { - const int idiv = 2 + i; // 2 - 6 input "resistors". - const int size = idiv << 16; - const double n = idiv; - opampModel.reset(); - summer[i] = new unsigned short[size]; - - for (int vi = 0; vi < size; vi++) - { - const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */ - summer[i][vi] = getNormalizedValue(opampModel.solve(n, vin)); - } - } + const double envI = envDac.getOutput(i); + voiceDC[i] = 5. * VOLTAGE_SKEW + (0.2143 * envI); } + } -// #pragma omp section - { -#ifdef _OPENMP - OpAmp opampModel( - std::vector( - std::begin(opamp_voltage), - std::end(opamp_voltage)), - Vddt, - vmin, - vmax); -#endif - // The audio mixer operates at n ~ 8/6, and has 8 fundamentally different - // input configurations (0 - 7 input "resistors"). - // - // All "on", transistors are modeled as one - see comments above for - // the filter summer. - for (int i = 0; i < 8; i++) - { - const int idiv = (i == 0) ? 1 : i; - const int size = (i == 0) ? 1 : i << 16; - const double n = i * 8.0 / 6.0; - opampModel.reset(); - mixer[i] = new unsigned short[size]; - - for (int vi = 0; vi < size; vi++) - { - const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */ - mixer[i][vi] = getNormalizedValue(opampModel.solve(n, vin)); - } - } - } + // Create lookup tables for gains / summers. -// #pragma omp section + // + // We spawn six threads to calculate these tables in parallel + // + auto filterSummer = [this] + { + OpAmp opampModel( + std::vector( + std::begin(opamp_voltage), + std::end(opamp_voltage)), + Vddt, + vmin, + vmax); + + buildSummerTable(opampModel); + }; + + auto filterMixer = [this] + { + OpAmp opampModel( + std::vector( + std::begin(opamp_voltage), + std::end(opamp_voltage)), + Vddt, + vmin, + vmax); + + buildMixerTable(opampModel, 8.0 / 6.0); + }; + + auto filterGain = [this] + { + OpAmp opampModel( + std::vector( + std::begin(opamp_voltage), + std::end(opamp_voltage)), + Vddt, + vmin, + vmax); + + buildVolumeTable(opampModel, 12.0); + }; + + auto filterResonance = [this] + { + OpAmp opampModel( + std::vector( + std::begin(opamp_voltage), + std::end(opamp_voltage)), + Vddt, + vmin, + vmax); + + // build temp n table + double resonance_n[16]; + for (int n8 = 0; n8 < 16; n8++) { -#ifdef _OPENMP - OpAmp opampModel( - std::vector( - std::begin(opamp_voltage), - std::end(opamp_voltage)), - Vddt, - vmin, - vmax); -#endif - // 4 bit "resistor" ladders in the audio output gain - // necessitate 16 gain tables. - // From die photographs of the volume "resistor" ladders - // it follows that gain ~ vol/12 (assuming ideal - // op-amps and ideal "resistors"). - for (int n8 = 0; n8 < 16; n8++) - { - const int size = 1 << 16; - const double n = n8 / 12.0; - opampModel.reset(); - gain_vol[n8] = new unsigned short[size]; - - for (int vi = 0; vi < size; vi++) - { - const double vin = vmin + vi / N16; /* vmin .. vmax */ - gain_vol[n8][vi] = getNormalizedValue(opampModel.solve(n, vin)); - } - } + resonance_n[n8] = (~n8 & 0xf) / 8.0; } -// #pragma omp section - { -#ifdef _OPENMP - OpAmp opampModel( - std::vector( - std::begin(opamp_voltage), - std::end(opamp_voltage)), - Vddt, - vmin, - vmax); -#endif - // 4 bit "resistor" ladders in the bandpass resonance gain - // necessitate 16 gain tables. - // From die photographs of the bandpass "resistor" ladders - // it follows that 1/Q ~ ~res/8 (assuming ideal - // op-amps and ideal "resistors"). - for (int n8 = 0; n8 < 16; n8++) - { - const int size = 1 << 16; - const double n = (~n8 & 0xf) / 8.0; - opampModel.reset(); - gain_res[n8] = new unsigned short[size]; - - for (int vi = 0; vi < size; vi++) - { - const double vin = vmin + vi / N16; /* vmin .. vmax */ - gain_res[n8][vi] = getNormalizedValue(opampModel.solve(n, vin)); - } - } - } + buildResonanceTable(opampModel, resonance_n); + }; + + auto filterVcrVg = [this] + { + const double nVddt = N16 * (Vddt - vmin); -// #pragma omp section + for (unsigned int i = 0; i < (1 << 16); i++) { - const double nVddt = N16 * (Vddt - vmin); - - for (unsigned int i = 0; i < (1 << 16); i++) - { - // The table index is right-shifted 16 times in order to fit in - // 16 bits; the argument to sqrt is thus multiplied by (1 << 16). - const double tmp = nVddt - sqrt(static_cast(i << 16)); - assert(tmp > -0.5 && tmp < 65535.5); - vcr_nVg[i] = static_cast(tmp + 0.5); - } + // The table index is right-shifted 16 times in order to fit in + // 16 bits; the argument to sqrt is thus multiplied by (1 << 16). + const double tmp = nVddt - std::sqrt(static_cast(i << 16)); + assert(tmp > -0.5 && tmp < 65535.5); + vcr_nVg[i] = static_cast(tmp + 0.5); } + }; -// #pragma omp section + auto filterVcrIds = [this] + { + // EKV model: + // + // Ids = Is * (if - ir) + // Is = (2 * u*Cox * Ut^2)/k * W/L + // if = ln^2(1 + e^((k*(Vg - Vt) - Vs)/(2*Ut)) + // ir = ln^2(1 + e^((k*(Vg - Vt) - Vd)/(2*Ut)) + + // moderate inversion characteristic current + // will be multiplied by uCox later + const double Is = (2. * Ut * Ut) * WL_vcr; + + // Normalized current factor for 1 cycle at 1MHz. + const double N15 = norm * ((1 << 15) - 1); + const double n_Is = N15 * 1.0e-6 / C * Is; + + // kVgt_Vx = k*(Vg - Vt) - Vx + // I.e. if k != 1.0, Vg must be scaled accordingly. + const double r_N16_2Ut = 1.0 / (N16 * 2.0 * Ut); + for (int i = 0; i < (1 << 16); i++) { - // EKV model: - // - // Ids = Is * (if - ir) - // Is = (2 * u*Cox * Ut^2)/k * W/L - // if = ln^2(1 + e^((k*(Vg - Vt) - Vs)/(2*Ut)) - // ir = ln^2(1 + e^((k*(Vg - Vt) - Vd)/(2*Ut)) - - // moderate inversion characteristic current - const double Is = (2. * uCox * Ut * Ut) * WL_vcr; - - // Normalized current factor for 1 cycle at 1MHz. - const double N15 = norm * ((1 << 15) - 1); - const double n_Is = N15 * 1.0e-6 / C * Is; - - // kVgt_Vx = k*(Vg - Vt) - Vx - // I.e. if k != 1.0, Vg must be scaled accordingly. - for (int kVgt_Vx = 0; kVgt_Vx < (1 << 16); kVgt_Vx++) - { - const double log_term = log1p(exp((kVgt_Vx / N16) / (2. * Ut))); - // Scaled by m*2^15 - const double tmp = n_Is * log_term * log_term; - assert(tmp > -0.5 && tmp < 65535.5); - vcr_n_Ids_term[kVgt_Vx] = static_cast(tmp + 0.5); - } + const int kVgt_Vx = i - (1 << 15); + const double log_term = std::log1p(std::exp(kVgt_Vx * r_N16_2Ut)); + // Scaled by m*2^15 + vcr_n_Ids_term[i] = n_Is * log_term * log_term; } - } + }; + +#if defined(HAVE_CXX20) && defined(__cpp_lib_jthread) + using sidThread = std::jthread; +#else + using sidThread = std::thread; +#endif + + sidThread thdSummer(filterSummer); + sidThread thdMixer(filterMixer); + sidThread thdGain(filterGain); + sidThread thdResonance(filterResonance); + sidThread thdVcrVg(filterVcrVg); + sidThread thdVcrIds(filterVcrIds); + +#if !defined(HAVE_CXX20) || !defined(__cpp_lib_jthread) + thdSummer.join(); + thdMixer.join(); + thdGain.join(); + thdResonance.join(); + thdVcrVg.join(); + thdVcrIds.join(); +#endif } unsigned short* FilterModelConfig6581::getDAC(double adjustment) const @@ -314,15 +282,10 @@ unsigned short* FilterModelConfig6581::getDAC(double adjustment) const for (unsigned int i = 0; i < (1 << DAC_BITS); i++) { const double fcd = dac.getOutput(i); - f0_dac[i] = getNormalizedValue(dac_zero + fcd * dac_scale / (1 << DAC_BITS)); + f0_dac[i] = getNormalizedValue(dac_zero + fcd * dac_scale); } return f0_dac; } -std::unique_ptr FilterModelConfig6581::buildIntegrator() -{ - return MAKE_UNIQUE(Integrator6581, this, WL_snake); -} - } // namespace reSIDfp diff --git a/src/sound/resid-fp/FilterModelConfig6581.h b/src/sound/resid-fp/FilterModelConfig6581.h index 06fcc5ce8e..75a52dadbf 100644 --- a/src/sound/resid-fp/FilterModelConfig6581.h +++ b/src/sound/resid-fp/FilterModelConfig6581.h @@ -41,17 +41,18 @@ class Integrator6581; */ class FilterModelConfig6581 final : public FilterModelConfig { -private: - static const unsigned int DAC_BITS = 11; - private: static std::unique_ptr instance; // This allows access to the private constructor -#ifdef HAVE_CXX11 friend std::unique_ptr::deleter_type; -#else - friend class std::auto_ptr; -#endif + +private: + static constexpr unsigned int DAC_BITS = 11; + + /** + * Power bricks generate voltages slightly out of spec + */ + static constexpr double VOLTAGE_SKEW = 1.015; /// Transistor parameters. //@{ @@ -68,21 +69,36 @@ class FilterModelConfig6581 final : public FilterModelConfig /// DAC lookup table Dac dac; - /// VCR - 6581 only. + /// Voltage Controlled Resistors //@{ unsigned short vcr_nVg[1 << 16]; - unsigned short vcr_n_Ids_term[1 << 16]; + double vcr_n_Ids_term[1 << 16]; //@} + // Voice DC offset LUT + double voiceDC[256]; + private: double getDacZero(double adjustment) const { return dac_zero + (1. - adjustment); } FilterModelConfig6581(); - ~FilterModelConfig6581() DEFAULT; + ~FilterModelConfig6581() = default; + +protected: + /** + * On 6581 the DC offset varies between ~5.0V and ~5.214V depending on + * the envelope value. + */ + inline double getVoiceDC(unsigned int env) const override + { + return voiceDC[env]; + } public: static FilterModelConfig6581* getInstance(); + void setFilterRange(double adjustment); + /** * Construct an 11 bit cutoff frequency DAC output voltage table. * Ownership is transferred to the requester which becomes responsible @@ -93,17 +109,17 @@ class FilterModelConfig6581 final : public FilterModelConfig */ unsigned short* getDAC(double adjustment) const; - /** - * Construct an integrator solver. - * - * @return the integrator - */ - std::unique_ptr buildIntegrator(); + inline double getWL_snake() const { return WL_snake; } inline unsigned short getVcr_nVg(int i) const { return vcr_nVg[i]; } - inline unsigned short getVcr_n_Ids_term(int i) const { return vcr_n_Ids_term[i]; } + inline unsigned short getVcr_n_Ids_term(int i) const + { + const double tmp = vcr_n_Ids_term[i] * uCox; + assert(tmp > -0.5 && tmp < 65535.5); + return static_cast(tmp + 0.5); + } // only used if SLOPE_FACTOR is defined - inline double getUt() const { return Ut; } + inline constexpr double getUt() const { return Ut; } inline double getN16() const { return N16; } }; diff --git a/src/sound/resid-fp/FilterModelConfig8580.cpp b/src/sound/resid-fp/FilterModelConfig8580.cpp index e838a366cc..a0a0c9ad82 100644 --- a/src/sound/resid-fp/FilterModelConfig8580.cpp +++ b/src/sound/resid-fp/FilterModelConfig8580.cpp @@ -25,6 +25,10 @@ #include "Integrator8580.h" #include "OpAmp.h" +#include "sidcxx11.h" + +#include +#include namespace reSIDfp { @@ -57,7 +61,7 @@ namespace reSIDfp * E Rf|R2 RC * F Rf|R3 RC */ -const double resGain[16] = +constexpr double resGain[16] = { 1.4/1.0, // Rf/Ri 1.4 ((1.4*15.3)/(1.4+15.3))/1.0, // (Rf|R1)/Ri 1.28263 @@ -77,13 +81,13 @@ const double resGain[16] = ((1.4*4.7)/(1.4+4.7))/2.8, // (Rf|R3)/RC 0.385246 }; -const unsigned int OPAMP_SIZE = 21; +constexpr unsigned int OPAMP_SIZE = 21; /** * This is the SID 8580 op-amp voltage transfer function, measured on * CAP1B/CAP1A on a chip marked CSG 8580R5 1690 25. */ -const Spline::Point opamp_voltage[OPAMP_SIZE] = +constexpr Spline::Point opamp_voltage[OPAMP_SIZE] = { { 1.30, 8.91 }, // Approximate start of actual range { 4.76, 8.91 }, @@ -110,8 +114,12 @@ const Spline::Point opamp_voltage[OPAMP_SIZE] = std::unique_ptr FilterModelConfig8580::instance(nullptr); +std::mutex Instance8580_Lock; + FilterModelConfig8580* FilterModelConfig8580::getInstance() { + std::lock_guard lock(Instance8580_Lock); + if (!instance.get()) { instance.reset(new FilterModelConfig8580()); @@ -122,161 +130,89 @@ FilterModelConfig8580* FilterModelConfig8580::getInstance() FilterModelConfig8580::FilterModelConfig8580() : FilterModelConfig( - 0.30, // voice voltage range FIXME measure - 4.84, // voice DC voltage FIXME measure - 22e-9, // capacitor value - 9.09, // Vdd - 0.80, // Vth - 100e-6, // uCox + 0.24, // voice voltage range FIXME should theoretically be ~0,474V + 22e-9, // capacitor value + 9. * VOLTAGE_SKEW, // Vdd + 0.80, // Vth + 100e-6, // uCox opamp_voltage, OPAMP_SIZE ) { // Create lookup tables for gains / summers. -#ifndef _OPENMP - OpAmp opampModel( - std::vector( - std::begin(opamp_voltage), - std::end(opamp_voltage)), - Vddt, - vmin, - vmax); -#endif -// #pragma omp parallel sections + // + // We spawn four threads to calculate these tables in parallel + // + auto filterSummer = [this] { -// #pragma omp section - { -#ifdef _OPENMP - OpAmp opampModel( - std::vector( - std::begin(opamp_voltage), - std::end(opamp_voltage)), - Vddt, - vmin, - vmax); -#endif - // The filter summer operates at n ~ 1, and has 5 fundamentally different - // input configurations (2 - 6 input "resistors"). - // - // Note that all "on" transistors are modeled as one. This is not - // entirely accurate, since the input for each transistor is different, - // and transistors are not linear components. However modeling all - // transistors separately would be extremely costly. - for (int i = 0; i < 5; i++) - { - const int idiv = 2 + i; // 2 - 6 input "resistors". - const int size = idiv << 16; - const double n = idiv; - opampModel.reset(); - summer[i] = new unsigned short[size]; - - for (int vi = 0; vi < size; vi++) - { - const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */ - summer[i][vi] = getNormalizedValue(opampModel.solve(n, vin)); - } - } - } - -// #pragma omp section - { -#ifdef _OPENMP - OpAmp opampModel( - std::vector( - std::begin(opamp_voltage), - std::end(opamp_voltage)), - Vddt, - vmin, - vmax); -#endif - // The audio mixer operates at n ~ 8/5, and has 8 fundamentally different - // input configurations (0 - 7 input "resistors"). - // - // All "on", transistors are modeled as one - see comments above for - // the filter summer. - for (int i = 0; i < 8; i++) - { - const int idiv = (i == 0) ? 1 : i; - const int size = (i == 0) ? 1 : i << 16; - const double n = i * 8.0 / 5.0; - opampModel.reset(); - mixer[i] = new unsigned short[size]; - - for (int vi = 0; vi < size; vi++) - { - const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */ - mixer[i][vi] = getNormalizedValue(opampModel.solve(n, vin)); - } - } - } - -// #pragma omp section - { -#ifdef _OPENMP - OpAmp opampModel( - std::vector( - std::begin(opamp_voltage), - std::end(opamp_voltage)), - Vddt, - vmin, - vmax); + OpAmp opampModel( + std::vector( + std::begin(opamp_voltage), + std::end(opamp_voltage)), + Vddt, + vmin, + vmax); + + buildSummerTable(opampModel); + }; + + auto filterMixer = [this] + { + OpAmp opampModel( + std::vector( + std::begin(opamp_voltage), + std::end(opamp_voltage)), + Vddt, + vmin, + vmax); + + buildMixerTable(opampModel, 8.0 / 5.0); + }; + + auto filterGain = [this] + { + OpAmp opampModel( + std::vector( + std::begin(opamp_voltage), + std::end(opamp_voltage)), + Vddt, + vmin, + vmax); + + buildVolumeTable(opampModel, 16.0); + }; + + auto filterResonance = [this] + { + OpAmp opampModel( + std::vector( + std::begin(opamp_voltage), + std::end(opamp_voltage)), + Vddt, + vmin, + vmax); + + buildResonanceTable(opampModel, resGain); + }; + +#if defined(HAVE_CXX20) && defined(__cpp_lib_jthread) + using sidThread = std::jthread; +#else + using sidThread = std::thread; #endif - // 4 bit "resistor" ladders in the audio output gain - // necessitate 16 gain tables. - // From die photographs of the volume "resistor" ladders - // it follows that gain ~ vol/16 (assuming ideal - // op-amps and ideal "resistors"). - for (int n8 = 0; n8 < 16; n8++) - { - const int size = 1 << 16; - const double n = n8 / 16.0; - opampModel.reset(); - gain_vol[n8] = new unsigned short[size]; - for (int vi = 0; vi < size; vi++) - { - const double vin = vmin + vi / N16; /* vmin .. vmax */ - gain_vol[n8][vi] = getNormalizedValue(opampModel.solve(n, vin)); - } - } - } + sidThread thdSummer(filterSummer); + sidThread thdMixer(filterMixer); + sidThread thdGain(filterGain); + sidThread thdResonance(filterResonance); -// #pragma omp section - { -#ifdef _OPENMP - OpAmp opampModel( - std::vector( - std::begin(opamp_voltage), - std::end(opamp_voltage)), - Vddt, - vmin, - vmax); +#if !defined(HAVE_CXX20) || !defined(__cpp_lib_jthread) + thdSummer.join(); + thdMixer.join(); + thdGain.join(); + thdResonance.join(); #endif - // 4 bit "resistor" ladders in the bandpass resonance gain - // necessitate 16 gain tables. - // From die photographs of the bandpass "resistor" ladders - // it follows that 1/Q ~ 2^((4 - res)/8) (assuming ideal - // op-amps and ideal "resistors"). - for (int n8 = 0; n8 < 16; n8++) - { - const int size = 1 << 16; - opampModel.reset(); - gain_res[n8] = new unsigned short[size]; - - for (int vi = 0; vi < size; vi++) - { - const double vin = vmin + vi / N16; /* vmin .. vmax */ - gain_res[n8][vi] = getNormalizedValue(opampModel.solve(resGain[n8], vin)); - } - } - } - } -} - -std::unique_ptr FilterModelConfig8580::buildIntegrator() -{ - return MAKE_UNIQUE(Integrator8580, this); } } // namespace reSIDfp diff --git a/src/sound/resid-fp/FilterModelConfig8580.h b/src/sound/resid-fp/FilterModelConfig8580.h index 509171bc3f..72a0559296 100644 --- a/src/sound/resid-fp/FilterModelConfig8580.h +++ b/src/sound/resid-fp/FilterModelConfig8580.h @@ -42,25 +42,30 @@ class FilterModelConfig8580 final : public FilterModelConfig private: static std::unique_ptr instance; // This allows access to the private constructor -#ifdef HAVE_CXX11 friend std::unique_ptr::deleter_type; -#else - friend class std::auto_ptr; -#endif + +private: + /** + * Reference voltage generated from Vcc by a voltage divider + */ + static constexpr double Vref = 4.75; + + /** + * Power bricks generate voltages slightly out of spec + */ + static constexpr double VOLTAGE_SKEW = 1.01; private: FilterModelConfig8580(); - ~FilterModelConfig8580() DEFAULT; + ~FilterModelConfig8580() = default; + +protected: + inline double getVoiceDC(unsigned int) const override { return getVref(); } public: static FilterModelConfig8580* getInstance(); - /** - * Construct an integrator solver. - * - * @return the integrator - */ - std::unique_ptr buildIntegrator(); + inline constexpr double getVref() const { return Vref * VOLTAGE_SKEW; } }; } // namespace reSIDfp diff --git a/src/sound/resid-fp/Integrator.h b/src/sound/resid-fp/Integrator.h new file mode 100644 index 0000000000..e8b5ec0b7d --- /dev/null +++ b/src/sound/resid-fp/Integrator.h @@ -0,0 +1,47 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2024 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004, 2010 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef INTEGRATOR_H +#define INTEGRATOR_H + +namespace reSIDfp +{ + +class Integrator +{ +protected: + mutable int vx; + mutable int vc; + + Integrator() : + vx(0), + vc(0) {} + +public: + virtual int solve(int vi) const = 0; + + virtual ~Integrator() = default; +}; + +} // namespace reSIDfp + +#endif diff --git a/src/sound/resid-fp/Integrator6581.cpp b/src/sound/resid-fp/Integrator6581.cpp index 490be9b5c7..0a48f5c49e 100644 --- a/src/sound/resid-fp/Integrator6581.cpp +++ b/src/sound/resid-fp/Integrator6581.cpp @@ -18,8 +18,80 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#define INTEGRATOR_CPP - #include "Integrator6581.h" -// This is needed when compiling with --disable-inline +#ifdef SLOPE_FACTOR +# include +# include "sidcxx11.h" +#endif + +namespace reSIDfp +{ + +int Integrator6581::solve(int vi) const +{ + // Make sure Vgst>0 so we're not in subthreshold mode + assert(vx < nVddt); + + // Check that transistor is actually in triode mode + // Vds < Vgs - Vth + assert(vi < nVddt); + + // "Snake" voltages for triode mode calculation. + const unsigned int Vgst = nVddt - vx; + const unsigned int Vgdt = nVddt - vi; + + const unsigned int Vgst_2 = Vgst * Vgst; + const unsigned int Vgdt_2 = Vgdt * Vgdt; + + // "Snake" current, scaled by (1/m)*2^13*m*2^16*m*2^16*2^-15 = m*2^30 + const int n_I_snake = fmc.getNormalizedCurrentFactor(wlSnake) * (static_cast(Vgst_2 - Vgdt_2) >> 15); + + // VCR gate voltage. // Scaled by m*2^16 + // Vg = Vddt - sqrt(((Vddt - Vw)^2 + Vgdt^2)/2) + const int nVg = static_cast(fmc.getVcr_nVg((nVddt_Vw_2 + (Vgdt_2 >> 1)) >> 16)); +#ifdef SLOPE_FACTOR + const double nVp = static_cast(nVg - nVt) / n; // Pinch-off voltage + const int kVgt = static_cast(nVp + 0.5) - nVmin; +#else + const int kVgt = (nVg - nVt) - nVmin; +#endif + + // VCR voltages for EKV model table lookup. + const int kVgt_Vs = (kVgt - vx) + (1 << 15); + assert((kVgt_Vs >= 0) && (kVgt_Vs < (1 << 16))); + const int kVgt_Vd = (kVgt - vi) + (1 << 15); + assert((kVgt_Vd >= 0) && (kVgt_Vd < (1 << 16))); + + // VCR current, scaled by m*2^15*2^15 = m*2^30 + const unsigned int If = static_cast(fmc.getVcr_n_Ids_term(kVgt_Vs)) << 15; + const unsigned int Ir = static_cast(fmc.getVcr_n_Ids_term(kVgt_Vd)) << 15; +#ifdef SLOPE_FACTOR + const double iVcr = static_cast(If - Ir); + const int n_I_vcr = static_cast(iVcr * n); +#else + const int n_I_vcr = If - Ir; +#endif + +#ifdef SLOPE_FACTOR + // estimate new slope factor based on gate voltage + constexpr double gamma = 1.0; // body effect factor + constexpr double phi = 0.8; // bulk Fermi potential + const double Vp = nVp / fmc.getN16(); + n = 1. + (gamma / (2. * std::sqrt(Vp + phi + 4. * fmc.getUt()))); + assert((n > 1.2) && (n < 1.8)); +#endif + + // Change in capacitor charge. + vc += n_I_snake + n_I_vcr; + + // vx = g(vc) + const int tmp = (vc >> 15) + (1 << 15); + assert(tmp < (1 << 16)); + vx = fmc.getOpampRev(tmp); + + // Return vo. + return vx - (vc >> 14); +} + +} // namespace reSIDfp diff --git a/src/sound/resid-fp/Integrator6581.h b/src/sound/resid-fp/Integrator6581.h index 5bdeca37d8..71db373425 100644 --- a/src/sound/resid-fp/Integrator6581.h +++ b/src/sound/resid-fp/Integrator6581.h @@ -1,7 +1,7 @@ /* * This file is part of libsidplayfp, a SID player engine. * - * Copyright 2011-2022 Leandro Nini + * Copyright 2011-2023 Leandro Nini * Copyright 2007-2010 Antti Lankila * Copyright 2004, 2010 Dag Lem * @@ -23,6 +23,7 @@ #ifndef INTEGRATOR6581_H #define INTEGRATOR6581_H +#include "Integrator.h" #include "FilterModelConfig6581.h" #include @@ -33,10 +34,6 @@ // actually produces worse results, needs investigation //#define SLOPE_FACTOR -#ifdef SLOPE_FACTOR -# include -#endif - #include "siddefs-fp.h" namespace reSIDfp @@ -164,12 +161,10 @@ namespace reSIDfp * * Vg = nVddt - sqrt(((nVddt - vi)^2 + (nVddt - Vw)^2)/2) */ -class Integrator6581 +class Integrator6581 : public Integrator { private: - unsigned int nVddt_Vw_2; - mutable int vx; - mutable int vc; + const double wlSnake; #ifdef SLOPE_FACTOR // Slope factor n = 1/k @@ -177,109 +172,32 @@ class Integrator6581 // k = Cox/(Cox+Cdep) ~ 0.7 (depends on gate voltage) mutable double n; #endif + + unsigned int nVddt_Vw_2; + const unsigned short nVddt; const unsigned short nVt; const unsigned short nVmin; - const unsigned short nSnake; - const FilterModelConfig6581* fmc; + FilterModelConfig6581& fmc; public: - Integrator6581(const FilterModelConfig6581* fmc, - double WL_snake) : - nVddt_Vw_2(0), - vx(0), - vc(0), + Integrator6581(FilterModelConfig6581& fmc) : + wlSnake(fmc.getWL_snake()), #ifdef SLOPE_FACTOR n(1.4), #endif - nVddt(fmc->getNormalizedValue(fmc->getVddt())), - nVt(fmc->getNormalizedValue(fmc->getVth())), - nVmin(fmc->getNVmin()), - nSnake(fmc->getNormalizedCurrentFactor(WL_snake)), + nVddt_Vw_2(0), + nVddt(fmc.getNormalizedValue(fmc.getVddt())), + nVt(fmc.getNormalizedValue(fmc.getVth())), + nVmin(fmc.getNVmin()), fmc(fmc) {} void setVw(unsigned short Vw) { nVddt_Vw_2 = ((nVddt - Vw) * (nVddt - Vw)) >> 1; } - int solve(int vi) const; + int solve(int vi) const override; }; } // namespace reSIDfp -#if RESID_INLINING || defined(INTEGRATOR_CPP) - -namespace reSIDfp -{ - -RESID_INLINE -int Integrator6581::solve(int vi) const -{ - // Make sure Vgst>0 so we're not in subthreshold mode - assert(vx < nVddt); - - // Check that transistor is actually in triode mode - // Vds < Vgs - Vth - assert(vi < nVddt); - - // "Snake" voltages for triode mode calculation. - const unsigned int Vgst = nVddt - vx; - const unsigned int Vgdt = nVddt - vi; - - const unsigned int Vgst_2 = Vgst * Vgst; - const unsigned int Vgdt_2 = Vgdt * Vgdt; - - // "Snake" current, scaled by (1/m)*2^13*m*2^16*m*2^16*2^-15 = m*2^30 - const int n_I_snake = nSnake * (static_cast(Vgst_2 - Vgdt_2) >> 15); - - // VCR gate voltage. // Scaled by m*2^16 - // Vg = Vddt - sqrt(((Vddt - Vw)^2 + Vgdt^2)/2) - const int nVg = static_cast(fmc->getVcr_nVg((nVddt_Vw_2 + (Vgdt_2 >> 1)) >> 16)); -#ifdef SLOPE_FACTOR - const double nVp = static_cast(nVg - nVt) / n; // Pinch-off voltage - const int kVgt = static_cast(nVp + 0.5) - nVmin; -#else - const int kVgt = (nVg - nVt) - nVmin; -#endif - - // VCR voltages for EKV model table lookup. - const int kVgt_Vs = (vx < kVgt) ? kVgt - vx : 0; - assert(kVgt_Vs < (1 << 16)); - const int kVgt_Vd = (vi < kVgt) ? kVgt - vi : 0; - assert(kVgt_Vd < (1 << 16)); - - // VCR current, scaled by m*2^15*2^15 = m*2^30 - const unsigned int If = static_cast(fmc->getVcr_n_Ids_term(kVgt_Vs)) << 15; - const unsigned int Ir = static_cast(fmc->getVcr_n_Ids_term(kVgt_Vd)) << 15; -#ifdef SLOPE_FACTOR - const double iVcr = static_cast(If - Ir); - const int n_I_vcr = static_cast(iVcr * n); -#else - const int n_I_vcr = If - Ir; -#endif - -#ifdef SLOPE_FACTOR - // estimate new slope factor based on gate voltage - const double gamma = 1.0; // body effect factor - const double phi = 0.8; // bulk Fermi potential - const double Vp = nVp / fmc->getN16(); - n = 1. + (gamma / (2. * sqrt(Vp + phi + 4. * fmc->getUt()))); - assert((n > 1.2) && (n < 1.8)); -#endif - - // Change in capacitor charge. - vc += n_I_snake + n_I_vcr; - - // vx = g(vc) - const int tmp = (vc >> 15) + (1 << 15); - assert(tmp < (1 << 16)); - vx = fmc->getOpampRev(tmp); - - // Return vo. - return vx - (vc >> 14); -} - -} // namespace reSIDfp - -#endif - #endif diff --git a/src/sound/resid-fp/Integrator8580.cpp b/src/sound/resid-fp/Integrator8580.cpp index 6fba9521bc..762442d920 100644 --- a/src/sound/resid-fp/Integrator8580.cpp +++ b/src/sound/resid-fp/Integrator8580.cpp @@ -18,8 +18,36 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#define INTEGRATOR8580_CPP - #include "Integrator8580.h" -// This is needed when compiling with --disable-inline +namespace reSIDfp +{ + +int Integrator8580::solve(int vi) const +{ + // Make sure we're not in subthreshold mode + assert(vx < nVgt); + + // DAC voltages + const unsigned int Vgst = nVgt - vx; + const unsigned int Vgdt = (vi < nVgt) ? nVgt - vi : 0; // triode/saturation mode + + const unsigned int Vgst_2 = Vgst * Vgst; + const unsigned int Vgdt_2 = Vgdt * Vgdt; + + // DAC current, scaled by (1/m)*2^13*m*2^16*m*2^16*2^-15 = m*2^30 + const int n_I_dac = n_dac * (static_cast(Vgst_2 - Vgdt_2) >> 15); + + // Change in capacitor charge. + vc += n_I_dac; + + // vx = g(vc) + const int tmp = (vc >> 15) + (1 << 15); + assert(tmp < (1 << 16)); + vx = fmc.getOpampRev(tmp); + + // Return vo. + return vx - (vc >> 14); +} + +} // namespace reSIDfp diff --git a/src/sound/resid-fp/Integrator8580.h b/src/sound/resid-fp/Integrator8580.h index db9e46b058..857d22ca29 100644 --- a/src/sound/resid-fp/Integrator8580.h +++ b/src/sound/resid-fp/Integrator8580.h @@ -23,6 +23,7 @@ #ifndef INTEGRATOR8580_H #define INTEGRATOR8580_H +#include "Integrator.h" #include "FilterModelConfig8580.h" #include @@ -51,21 +52,16 @@ namespace reSIDfp * * Rfc gate voltage is generated by an OP Amp and depends on chip temperature. */ -class Integrator8580 +class Integrator8580 : public Integrator { private: - mutable int vx; - mutable int vc; - unsigned short nVgt; unsigned short n_dac; - const FilterModelConfig8580* fmc; + FilterModelConfig8580& fmc; public: - Integrator8580(const FilterModelConfig8580* fmc) : - vx(0), - vc(0), + Integrator8580(FilterModelConfig8580& fmc) : fmc(fmc) { setV(1.5); @@ -78,7 +74,7 @@ class Integrator8580 { // Normalized current factor, 1 cycle at 1MHz. // Fit in 5 bits. - n_dac = fmc->getNormalizedCurrentFactor(wl); + n_dac = fmc.getNormalizedCurrentFactor(wl); } /** @@ -87,56 +83,19 @@ class Integrator8580 void setV(double v) { // Gate voltage is controlled by the switched capacitor voltage divider - // Ua = Ue * v = 4.76v 1 1.0 && v < 2.0); - const double Vg = 4.76 * v; - const double Vgt = Vg - fmc->getVth(); + const double Vg = fmc.getVref() * v; + const double Vgt = Vg - fmc.getVth(); // Vg - Vth, normalized so that translated values can be subtracted: // Vgt - x = (Vgt - t) - (x - t) - nVgt = fmc->getNormalizedValue(Vgt); + nVgt = fmc.getNormalizedValue(Vgt); } - int solve(int vi) const; + int solve(int vi) const override; }; } // namespace reSIDfp -#if RESID_INLINING || defined(INTEGRATOR8580_CPP) - -namespace reSIDfp -{ - -RESID_INLINE -int Integrator8580::solve(int vi) const -{ - // Make sure we're not in subthreshold mode - assert(vx < nVgt); - - // DAC voltages - const unsigned int Vgst = nVgt - vx; - const unsigned int Vgdt = (vi < nVgt) ? nVgt - vi : 0; // triode/saturation mode - - const unsigned int Vgst_2 = Vgst * Vgst; - const unsigned int Vgdt_2 = Vgdt * Vgdt; - - // DAC current, scaled by (1/m)*2^13*m*2^16*m*2^16*2^-15 = m*2^30 - const int n_I_dac = n_dac * (static_cast(Vgst_2 - Vgdt_2) >> 15); - - // Change in capacitor charge. - vc += n_I_dac; - - // vx = g(vc) - const int tmp = (vc >> 15) + (1 << 15); - assert(tmp < (1 << 16)); - vx = fmc->getOpampRev(tmp); - - // Return vo. - return vx - (vc >> 14); -} - -} // namespace reSIDfp - -#endif - #endif diff --git a/src/sound/resid-fp/OpAmp.cpp b/src/sound/resid-fp/OpAmp.cpp index b26b2efcb7..ed4f2700c0 100644 --- a/src/sound/resid-fp/OpAmp.cpp +++ b/src/sound/resid-fp/OpAmp.cpp @@ -1,7 +1,7 @@ /* * This file is part of libsidplayfp, a SID player engine. * - * Copyright 2011-2015 Leandro Nini + * Copyright 2011-2024 Leandro Nini * Copyright 2007-2010 Antti Lankila * * This program is free software; you can redistribute it and/or modify @@ -28,7 +28,7 @@ namespace reSIDfp { -const double EPSILON = 1e-8; +constexpr double EPSILON = 1e-8; double OpAmp::solve(double n, double vi) const { @@ -48,7 +48,7 @@ double OpAmp::solve(double n, double vi) const // Calculate f and df. - Spline::Point out = opamp->evaluate(x); + Spline::Point out = opamp.evaluate(x); const double vo = out.x; const double dvo = out.y; @@ -64,9 +64,9 @@ double OpAmp::solve(double n, double vi) const // Newton-Raphson step: xk1 = xk - f(xk)/f'(xk) x -= f / df; - if (unlikely(fabs(x - xk) < EPSILON)) + if (unlikely(std::fabs(x - xk) < EPSILON)) { - out = opamp->evaluate(x); + out = opamp.evaluate(x); return out.x; } diff --git a/src/sound/resid-fp/OpAmp.h b/src/sound/resid-fp/OpAmp.h index f048b1845f..ec9d68cbb9 100644 --- a/src/sound/resid-fp/OpAmp.h +++ b/src/sound/resid-fp/OpAmp.h @@ -1,7 +1,7 @@ /* * This file is part of libsidplayfp, a SID player engine. * - * Copyright 2011-2023 Leandro Nini + * Copyright 2011-2024 Leandro Nini * Copyright 2007-2010 Antti Lankila * Copyright 2004,2010 Dag Lem * @@ -23,7 +23,6 @@ #ifndef OPAMP_H #define OPAMP_H -#include #include #include "Spline.h" @@ -72,13 +71,13 @@ class OpAmp { private: /// Current root position (cached as guess to speed up next iteration) - mutable double x; + mutable double x = 0.; const double Vddt; const double vmin; const double vmax; - std::unique_ptr const opamp; + Spline opamp; public: /** @@ -89,14 +88,13 @@ class OpAmp * @param vmin * @param vmax */ - OpAmp(const std::vector &opamp, double Vddt, + OpAmp(const std::vector &opamp_voltages, double Vddt, double vmin, double vmax ) : - x(0.), Vddt(Vddt), vmin(vmin), vmax(vmax), - opamp(new Spline(opamp)) {} + opamp(opamp_voltages) {} /** * Reset root position diff --git a/src/sound/resid-fp/SID.cpp b/src/sound/resid-fp/SID.cpp index 840d264e28..5b5506bc3c 100644 --- a/src/sound/resid-fp/SID.cpp +++ b/src/sound/resid-fp/SID.cpp @@ -1,7 +1,7 @@ /* * This file is part of libsidplayfp, a SID player engine. * - * Copyright 2011-2016 Leandro Nini + * Copyright 2011-2024 Leandro Nini * Copyright 2007-2010 Antti Lankila * Copyright 2004 Dag Lem * @@ -26,11 +26,12 @@ #include +#include "sidcxx11.h" + #include "array.h" #include "Dac.h" #include "Filter6581.h" #include "Filter8580.h" -#include "Potentiometer.h" #include "WaveformCalculator.h" #include "resample/TwoPassSincResampler.h" #include "resample/ZeroOrderResampler.h" @@ -38,8 +39,8 @@ namespace reSIDfp { -const unsigned int ENV_DAC_BITS = 8; -const unsigned int OSC_DAC_BITS = 12; +constexpr unsigned int ENV_DAC_BITS = 8; +constexpr unsigned int OSC_DAC_BITS = 12; /** * The waveform D/A converter introduces a DC offset in the signal @@ -106,8 +107,8 @@ const unsigned int OSC_DAC_BITS = 12; * On my 6581R4AR has 0x3A as the only value giving the same output level as 1.prg */ //@{ -unsigned int constexpr OFFSET_6581 = 0x380; -unsigned int constexpr OFFSET_8580 = 0x9c0; +constexpr unsigned int OFFSET_6581 = 0x380; +constexpr unsigned int OFFSET_8580 = 0x9c0; //@} /** @@ -128,31 +129,24 @@ unsigned int constexpr OFFSET_8580 = 0x9c0; * [2]: http://noname.c64.org/csdb/forums/?roomid=11&topicid=29025&showallposts=1 */ //@{ -int constexpr BUS_TTL_6581 = 0x01d00; -int constexpr BUS_TTL_8580 = 0xa2000; +constexpr int BUS_TTL_6581 = 0x01d00; +constexpr int BUS_TTL_8580 = 0xa2000; //@} SID::SID() : filter6581(new Filter6581()), filter8580(new Filter8580()), - externalFilter(new ExternalFilter()), resampler(nullptr), - potX(new Potentiometer()), - potY(new Potentiometer()) + cws(AVERAGE) { - voice[0].reset(new Voice()); - voice[1].reset(new Voice()); - voice[2].reset(new Voice()); - - muted[0] = muted[1] = muted[2] = false; - + setChipModel(MOS6581); reset(); - setChipModel(MOS8580); } SID::~SID() { - // Needed to delete auto_ptr with complete type + delete filter6581; + delete filter8580; } void SID::setFilter6581Curve(double filterCurve) @@ -160,6 +154,11 @@ void SID::setFilter6581Curve(double filterCurve) filter6581->setFilterCurve(filterCurve); } +void SID::setFilter6581Range(double adjustment) +{ + filter6581->setFilterRange(adjustment); +} + void SID::setFilter8580Curve(double filterCurve) { filter8580->setFilterCurve(filterCurve); @@ -178,7 +177,7 @@ void SID::voiceSync(bool sync) // Synchronize the 3 waveform generators. for (int i = 0; i < 3; i++) { - voice[i]->wave()->synchronize(voice[(i + 1) % 3]->wave(), voice[(i + 2) % 3]->wave()); + voice[i].wave()->synchronize(voice[(i + 1) % 3].wave(), voice[(i + 2) % 3].wave()); } } @@ -187,10 +186,10 @@ void SID::voiceSync(bool sync) for (int i = 0; i < 3; i++) { - WaveformGenerator* const wave = voice[i]->wave(); + WaveformGenerator* const wave = voice[i].wave(); const unsigned int freq = wave->readFreq(); - if (wave->readTest() || freq == 0 || !voice[(i + 1) % 3]->wave()->readSync()) + if (wave->readTest() || freq == 0 || !voice[(i + 1) % 3].wave()->readSync()) { continue; } @@ -210,12 +209,14 @@ void SID::setChipModel(ChipModel model) switch (model) { case MOS6581: - filter = filter6581.get(); + filter = filter6581; + scaleFactor = 3; modelTTL = BUS_TTL_6581; break; case MOS8580: - filter = filter8580.get(); + filter = filter8580; + scaleFactor = 5; modelTTL = BUS_TTL_8580; break; @@ -227,7 +228,7 @@ void SID::setChipModel(ChipModel model) // calculate waveform-related tables matrix_t* wavetables = WaveformCalculator::getInstance()->getWaveTable(); - matrix_t* pulldowntables = WaveformCalculator::getInstance()->buildPulldownTable(model); + matrix_t* pulldowntables = WaveformCalculator::getInstance()->buildPulldownTable(model, cws); // calculate envelope DAC table { @@ -247,7 +248,8 @@ void SID::setChipModel(ChipModel model) Dac dacBuilder(OSC_DAC_BITS); dacBuilder.kinkedDac(model); - const double offset = dacBuilder.getOutput(is6581 ? OFFSET_6581 : OFFSET_8580); + //const double offset = dacBuilder.getOutput(is6581 ? OFFSET_6581 : OFFSET_8580); + const double offset = dacBuilder.getOutput(0x7ff); for (unsigned int i = 0; i < (1 << OSC_DAC_BITS); i++) { @@ -259,11 +261,35 @@ void SID::setChipModel(ChipModel model) // set voice tables for (int i = 0; i < 3; i++) { - voice[i]->setEnvDAC(envDAC); - voice[i]->setWavDAC(oscDAC); - voice[i]->wave()->setModel(is6581); - voice[i]->wave()->setWaveformModels(wavetables); - voice[i]->wave()->setPulldownModels(pulldowntables); + voice[i].setEnvDAC(envDAC); + voice[i].setWavDAC(oscDAC); + voice[i].wave()->setModel(is6581); + voice[i].wave()->setWaveformModels(wavetables); + voice[i].wave()->setPulldownModels(pulldowntables); + } +} + +void SID::setCombinedWaveforms(CombinedWaveforms cws) +{ + switch (cws) + { + case AVERAGE: + case WEAK: + case STRONG: + break; + + default: + throw SIDError("Unknown combined waveforms type"); + } + + this->cws = cws; + + // rebuild waveform-related tables + matrix_t* pulldowntables = WaveformCalculator::getInstance()->buildPulldownTable(model, cws); + + for (int i = 0; i < 3; i++) + { + voice[i].wave()->setPulldownModels(pulldowntables); } } @@ -271,12 +297,12 @@ void SID::reset() { for (int i = 0; i < 3; i++) { - voice[i]->reset(); + voice[i].reset(); } filter6581->reset(); filter8580->reset(); - externalFilter->reset(); + externalFilter.reset(); if (resampler.get()) { @@ -299,22 +325,22 @@ unsigned char SID::read(int offset) switch (offset) { case 0x19: // X value of paddle - busValue = potX->readPOT(); + busValue = potX.readPOT(); busValueTtl = modelTTL; break; case 0x1a: // Y value of paddle - busValue = potY->readPOT(); + busValue = potY.readPOT(); busValueTtl = modelTTL; break; case 0x1b: // Voice #3 waveform output - busValue = voice[2]->wave()->readOSC(); + busValue = voice[2].wave()->readOSC(); busValueTtl = modelTTL; break; case 0x1c: // Voice #3 ADSR output - busValue = voice[2]->envelope()->readENV(); + busValue = voice[2].envelope()->readENV(); busValueTtl = modelTTL; break; @@ -337,87 +363,87 @@ void SID::write(int offset, unsigned char value) switch (offset) { case 0x00: // Voice #1 frequency (Low-byte) - voice[0]->wave()->writeFREQ_LO(value); + voice[0].wave()->writeFREQ_LO(value); break; case 0x01: // Voice #1 frequency (High-byte) - voice[0]->wave()->writeFREQ_HI(value); + voice[0].wave()->writeFREQ_HI(value); break; case 0x02: // Voice #1 pulse width (Low-byte) - voice[0]->wave()->writePW_LO(value); + voice[0].wave()->writePW_LO(value); break; case 0x03: // Voice #1 pulse width (bits #8-#15) - voice[0]->wave()->writePW_HI(value); + voice[0].wave()->writePW_HI(value); break; case 0x04: // Voice #1 control register - voice[0]->writeCONTROL_REG(muted[0] ? 0 : value); + voice[0].writeCONTROL_REG(value); break; case 0x05: // Voice #1 Attack and Decay length - voice[0]->envelope()->writeATTACK_DECAY(value); + voice[0].envelope()->writeATTACK_DECAY(value); break; case 0x06: // Voice #1 Sustain volume and Release length - voice[0]->envelope()->writeSUSTAIN_RELEASE(value); + voice[0].envelope()->writeSUSTAIN_RELEASE(value); break; case 0x07: // Voice #2 frequency (Low-byte) - voice[1]->wave()->writeFREQ_LO(value); + voice[1].wave()->writeFREQ_LO(value); break; case 0x08: // Voice #2 frequency (High-byte) - voice[1]->wave()->writeFREQ_HI(value); + voice[1].wave()->writeFREQ_HI(value); break; case 0x09: // Voice #2 pulse width (Low-byte) - voice[1]->wave()->writePW_LO(value); + voice[1].wave()->writePW_LO(value); break; case 0x0a: // Voice #2 pulse width (bits #8-#15) - voice[1]->wave()->writePW_HI(value); + voice[1].wave()->writePW_HI(value); break; case 0x0b: // Voice #2 control register - voice[1]->writeCONTROL_REG(muted[1] ? 0 : value); + voice[1].writeCONTROL_REG(value); break; case 0x0c: // Voice #2 Attack and Decay length - voice[1]->envelope()->writeATTACK_DECAY(value); + voice[1].envelope()->writeATTACK_DECAY(value); break; case 0x0d: // Voice #2 Sustain volume and Release length - voice[1]->envelope()->writeSUSTAIN_RELEASE(value); + voice[1].envelope()->writeSUSTAIN_RELEASE(value); break; case 0x0e: // Voice #3 frequency (Low-byte) - voice[2]->wave()->writeFREQ_LO(value); + voice[2].wave()->writeFREQ_LO(value); break; case 0x0f: // Voice #3 frequency (High-byte) - voice[2]->wave()->writeFREQ_HI(value); + voice[2].wave()->writeFREQ_HI(value); break; case 0x10: // Voice #3 pulse width (Low-byte) - voice[2]->wave()->writePW_LO(value); + voice[2].wave()->writePW_LO(value); break; case 0x11: // Voice #3 pulse width (bits #8-#15) - voice[2]->wave()->writePW_HI(value); + voice[2].wave()->writePW_HI(value); break; case 0x12: // Voice #3 control register - voice[2]->writeCONTROL_REG(muted[2] ? 0 : value); + voice[2].writeCONTROL_REG(value); break; case 0x13: // Voice #3 Attack and Decay length - voice[2]->envelope()->writeATTACK_DECAY(value); + voice[2].envelope()->writeATTACK_DECAY(value); break; case 0x14: // Voice #3 Sustain volume and Release length - voice[2]->envelope()->writeSUSTAIN_RELEASE(value); + voice[2].envelope()->writeSUSTAIN_RELEASE(value); break; case 0x15: // Filter cut off frequency (bits #0-#2) @@ -448,9 +474,9 @@ void SID::write(int offset, unsigned char value) voiceSync(false); } -void SID::setSamplingParameters(double clockFrequency, SamplingMethod method, double samplingFrequency, double highestAccurateFrequency) +void SID::setSamplingParameters(double clockFrequency, SamplingMethod method, double samplingFrequency) { - externalFilter->setClockFrequency(clockFrequency); + externalFilter.setClockFrequency(clockFrequency); switch (method) { @@ -459,7 +485,7 @@ void SID::setSamplingParameters(double clockFrequency, SamplingMethod method, do break; case RESAMPLE: - resampler.reset(TwoPassSincResampler::create(clockFrequency, samplingFrequency, highestAccurateFrequency)); + resampler.reset(TwoPassSincResampler::create(clockFrequency, samplingFrequency)); break; default: @@ -480,16 +506,16 @@ void SID::clockSilent(unsigned int cycles) for (int i = 0; i < delta_t; i++) { // clock waveform generators (can affect OSC3) - voice[0]->wave()->clock(); - voice[1]->wave()->clock(); - voice[2]->wave()->clock(); + voice[0].wave()->clock(); + voice[1].wave()->clock(); + voice[2].wave()->clock(); - voice[0]->wave()->output(voice[2]->wave()); - voice[1]->wave()->output(voice[0]->wave()); - voice[2]->wave()->output(voice[1]->wave()); + voice[0].wave()->output(voice[2].wave()); + voice[1].wave()->output(voice[0].wave()); + voice[2].wave()->output(voice[1].wave()); // clock ENV3 only - voice[2]->envelope()->clock(); + voice[2].envelope()->clock(); } cycles -= delta_t; diff --git a/src/sound/resid-fp/Spline.cpp b/src/sound/resid-fp/Spline.cpp index 50d55fef14..273fea032f 100644 --- a/src/sound/resid-fp/Spline.cpp +++ b/src/sound/resid-fp/Spline.cpp @@ -92,11 +92,11 @@ Spline::Point Spline::evaluate(double x) const { if ((x < c->x1) || (x > c->x2)) { - for (size_t i = 0; i < params.size(); i++) + for (const auto & param : params) { - if (x <= params[i].x2) + if (x <= param.x2) { - c = ¶ms[i]; + c = ¶m; break; } } diff --git a/src/sound/resid-fp/Spline.h b/src/sound/resid-fp/Spline.h index 6cc2b1edc0..c3ef1637bc 100644 --- a/src/sound/resid-fp/Spline.h +++ b/src/sound/resid-fp/Spline.h @@ -38,14 +38,14 @@ namespace reSIDfp class Spline { public: - typedef struct + using Point = struct { double x; double y; - } Point; + }; private: - typedef struct + using Param = struct { double x1; double x2; @@ -53,9 +53,9 @@ class Spline double b; double c; double d; - } Param; + }; - typedef std::vector ParamVector; + using ParamVector = std::vector; private: /// Interpolation parameters diff --git a/src/sound/resid-fp/Voice.h b/src/sound/resid-fp/Voice.h index fc7ed41b70..0fb708b1e0 100644 --- a/src/sound/resid-fp/Voice.h +++ b/src/sound/resid-fp/Voice.h @@ -23,14 +23,10 @@ #ifndef VOICE_H #define VOICE_H -#include - #include "siddefs-fp.h" #include "WaveformGenerator.h" #include "EnvelopeGenerator.h" -#include "sidcxx11.h" - namespace reSIDfp { @@ -40,9 +36,9 @@ namespace reSIDfp class Voice { private: - std::unique_ptr const waveformGenerator; + WaveformGenerator waveformGenerator; - std::unique_ptr const envelopeGenerator; + EnvelopeGenerator envelopeGenerator; /// The DAC LUT for analog waveform output float* wavDAC; //-V730_NOINIT this is initialized in the SID constructor @@ -67,23 +63,16 @@ class Voice * @return the voice analog output */ RESID_INLINE - int output(const WaveformGenerator* ringModulator) const + float output(const WaveformGenerator* ringModulator) { - unsigned int const wav = waveformGenerator->output(ringModulator); - unsigned int const env = envelopeGenerator->output(); + unsigned int const wav = waveformGenerator.output(ringModulator); + unsigned int const env = envelopeGenerator.output(); // DAC imperfections are emulated by using the digital output // as an index into a DAC lookup table. - return static_cast(wavDAC[wav] * envDAC[env]); + return wavDAC[wav] * envDAC[env]; } - /** - * Constructor. - */ - Voice() : - waveformGenerator(new WaveformGenerator()), - envelopeGenerator(new EnvelopeGenerator()) {} - /** * Set the analog DAC emulation for waveform generator. * Must be called before any operation. @@ -100,9 +89,9 @@ class Voice */ void setEnvDAC(float* dac) { envDAC = dac; } - WaveformGenerator* wave() const { return waveformGenerator.get(); } + WaveformGenerator* wave() { return &waveformGenerator; } - EnvelopeGenerator* envelope() const { return envelopeGenerator.get(); } + EnvelopeGenerator* envelope() { return &envelopeGenerator; } /** * Write control register. @@ -111,8 +100,8 @@ class Voice */ void writeCONTROL_REG(unsigned char control) { - waveformGenerator->writeCONTROL_REG(control); - envelopeGenerator->writeCONTROL_REG(control); + waveformGenerator.writeCONTROL_REG(control); + envelopeGenerator.writeCONTROL_REG(control); } /** @@ -120,8 +109,8 @@ class Voice */ void reset() { - waveformGenerator->reset(); - envelopeGenerator->reset(); + waveformGenerator.reset(); + envelopeGenerator.reset(); } }; diff --git a/src/sound/resid-fp/WaveformCalculator.cpp b/src/sound/resid-fp/WaveformCalculator.cpp index 74a93cce56..7e167bb187 100644 --- a/src/sound/resid-fp/WaveformCalculator.cpp +++ b/src/sound/resid-fp/WaveformCalculator.cpp @@ -21,66 +21,154 @@ #include "WaveformCalculator.h" +#include "sidcxx11.h" + +#include +#include #include namespace reSIDfp { +/** + * Combined waveform model parameters. + */ +using distance_t = float (*)(float, int); + +using CombinedWaveformConfig = struct +{ + distance_t distFunc; + float threshold; + float topbit; + float pulsestrength; + float distance1; + float distance2; +}; + +using cw_cache_t = std::map; + +cw_cache_t PULLDOWN_CACHE; + +std::mutex PULLDOWN_CACHE_Lock; + WaveformCalculator* WaveformCalculator::getInstance() { static WaveformCalculator instance; return &instance; } +// Distance functions +static float exponentialDistance(float distance, int i) +{ + return pow(distance, -i); +} + +MAYBE_UNUSED static float linearDistance(float distance, int i) +{ + return 1.f / (1.f + i * distance); +} + +static float quadraticDistance(float distance, int i) +{ + return 1.f / (1.f + (i*i) * distance); +} + /** * Parameters derived with the Monte Carlo method based on - * samplings by kevtris. Code and data available in the project repository [1]. + * samplings from real machines. + * Code and data available in the project repository [1]. + * Sampling program made by Dag Lem [2]. * * The score here reported is the acoustic error * calculated XORing the estimated and the sampled values. * In parentheses the number of mispredicted bits. * * [1] https://github.com/libsidplayfp/combined-waveforms + * [2] https://github.com/daglem/reDIP-SID/blob/master/research/combsample.d64 */ -const CombinedWaveformConfig config[2][5] = +const CombinedWaveformConfig configAverage[2][5] = { - { /* kevtris chip G (6581 R2) */ - {0.862147212f, 0.f, 10.8962431f, 2.50848103f }, // TS error 1941 (327/28672) - {0.932746708f, 2.07508397f, 1.03668225f, 1.14876997f }, // PT error 5992 (126/32768) - {0.860927045f, 2.43506575f, 0.908603609f, 1.07907593f }, // PS error 3693 (521/28672) - {0.741343081f, 0.0452554375f, 1.1439606f, 1.05711341f }, // PTS error 338 ( 29/28672) - {0.96f, 2.5f, 1.1f, 1.2f }, // NP guessed + { /* 6581 R3 0486S sampled by Trurl */ + // TS error 3555 (324/32768) [RMS: 73.98] + { exponentialDistance, 0.877322257f, 1.11349654f, 0.f, 2.14537621f, 9.08618164f }, + // PT error 4590 (124/32768) [RMS: 68.90] + { linearDistance, 0.941692829f, 1.f, 1.80072665f, 0.033124879f, 0.232303441f }, + // PS error 19352 (763/32768) [RMS: 96.91] + { linearDistance, 1.66494179f, 1.03760982f, 5.62705326f, 0.291590303f, 0.283631504f }, + // PTS error 5068 ( 94/32768) [RMS: 41.69] + { linearDistance, 1.09762526f, 0.975265801f, 1.52196741f, 0.151528224f, 0.841949463f }, + // NP guessed + { exponentialDistance, 0.96f, 1.f, 2.5f, 1.1f, 1.2f }, }, - { /* kevtris chip V (8580 R5) */ - {0.715788841f, 0.f, 1.32999945f, 2.2172699f }, // TS error 928 (135/32768) - {0.93500334f, 1.05977178f, 1.08629429f, 1.43518543f }, // PT error 7991 (212/32768) - {0.920648575f, 0.943601072f, 1.13034654f, 1.41881108f }, // PS error 12566 (394/32768) - {0.90921098f, 0.979807794f, 0.942194462f, 1.40958893f }, // PTS error 2092 ( 60/32768) - {0.95f, 1.15f, 1.f, 1.45f }, // NP guessed + { /* 8580 R5 1088 sampled by reFX-Mike */ + // TS error 10660 (353/32768) [RMS: 58.34] + { exponentialDistance, 0.853578329f, 1.09615636f, 0.f, 1.8819375f, 6.80794907f }, + // PT error 10635 (289/32768) [RMS: 108.81] + { exponentialDistance, 0.929835618f, 1.f, 1.12836814f, 1.10453653f, 1.48065746f }, + // PS error 12255 (554/32768) [RMS: 102.27] + { quadraticDistance, 0.911938608f, 0.996440411f, 1.2278074f, 0.000117214302f, 0.18948476f }, + // PTS error 6913 (127/32768) [RMS: 55.80] + { exponentialDistance, 0.938004673f, 1.04827631f, 1.21178246f, 0.915959001f, 1.42698038f }, + // NP guessed + { exponentialDistance, 0.95f, 1.f, 1.15f, 1.f, 1.45f }, }, }; -typedef float (*distance_t)(float, int); - -// Distance functions -static float exponentialDistance(float distance, int i) -{ - return pow(distance, -i); -} - -#if 0 -MAYBE_UNUSED static float linearDistance(float distance, int i) +const CombinedWaveformConfig configWeak[2][5] = { - return 1.f / (1.f + i * distance); -} -#endif + { /* 6581 R2 4383 sampled by ltx128 */ + // TS error 1474 (198/32768) [RMS: 62.81] + { exponentialDistance, 0.892563999f, 1.11905622f, 0.f, 2.21876144f, 9.63837719f }, + // PT error 612 (102/32768) [RMS: 43.71] + { linearDistance, 1.01262534f, 1.f, 2.46070528f, 0.0537485816f, 0.0986242667f }, + // PS error 8135 (575/32768) [RMS: 75.10] + { linearDistance, 2.14896345f, 1.0216713f, 10.5400085f, 0.244498149f, 0.126134038f }, + // PTS error 2489 (60/32768) [RMS: 24.41] + { linearDistance, 1.22330308f, 0.933797896f, 2.83245254f, 0.0615176819f, 0.323831677f }, + // NP guessed + { exponentialDistance, 0.96f, 1.f, 2.5f, 1.1f, 1.2f }, + }, + { /* 8580 R5 4887 sampled by reFX-Mike */ + // TS error 741 (76/32768) [RMS: 53.74] + { exponentialDistance, 0.812351167f, 1.1727736f, 0.f, 1.87459648f, 2.31578159f }, + // PT error 7199 (192/32768) [RMS: 88.43] + { exponentialDistance, 0.917997837f, 1.f, 1.01248944f, 1.05761552f, 1.37529826f }, + // PS error 9856 (332/32768) [RMS: 86.29] + { quadraticDistance, 0.968754232f, 1.00669801f, 1.29909098f, 0.00962483883f, 0.146850556f }, + // PTS error 4809 (60/32768) [RMS: 45.37] + { exponentialDistance, 0.941834152f, 1.06401193f, 0.991132736f, 0.995310068f, 1.41105855f }, + // NP guessed + { exponentialDistance, 0.95f, 1.f, 1.15f, 1.f, 1.45f }, + }, +}; -#if 0 -MAYBE_UNUSED static float quadraticDistance(float distance, int i) +const CombinedWaveformConfig configStrong[2][5] = { - return 1.f / (1.f + (i*i) * distance); -} -#endif + { /* 6581 R2 0384 sampled by Trurl */ + // TS error 20337 (1579/32768) [RMS: 88.57] + { exponentialDistance, 0.000637792516f, 1.56725872f, 0.f, 0.00036806846f, 1.51800942f }, + // PT error 5190 (238/32768) [RMS: 83.54] + { linearDistance, 0.924780309f, 1.f, 1.96809769f, 0.0888123438f, 0.234606609f }, + // PS error 31015 (2181/32768) [RMS: 114.99] + { linearDistance, 1.2328074f, 0.73079139f, 3.9719491f, 0.00156516861f, 0.314677745f }, + // PTS error 9874 (201/32768) [RMS: 52.30] + { linearDistance, 1.08558261f, 0.857638359f, 1.52781796f, 0.152927235f, 1.02657032f }, + // NP guessed + { exponentialDistance, 0.96f, 1.f, 2.5f, 1.1f, 1.2f }, + }, + { /* 8580 R5 1489 sampled by reFX-Mike */ + // TS error 4837 (388/32768) [RMS: 76.07] + { exponentialDistance, 0.89762634f, 56.7594185f, 0.f, 7.68995237f, 12.0754194f }, + // PT error 9266 (508/32768) [RMS: 127.83] + { exponentialDistance, 0.87147671f, 1.f, 1.44887495f, 1.05899632f, 1.43786001f }, + // PS error 13168 (718/32768) [RMS: 123.35] + { quadraticDistance, 0.89255774f, 1.2253896f, 1.75615835f, 0.0245045591f, 0.12982437f }, + // PTS error 6702 (300/32768) [RMS: 71.01] + { linearDistance, 0.91124934f, 0.963609755f, 0.909965038f, 1.07445884f, 1.82399702f }, + // NP guessed + { exponentialDistance, 0.95f, 1.f, 1.15f, 1.f, 1.45f }, + }, +}; /// Calculate triangle waveform static unsigned int triXor(unsigned int val) @@ -96,15 +184,17 @@ static unsigned int triXor(unsigned int val) * @param threshold * @param accumulator the high bits of the accumulator value */ -short calculatePulldown(float distancetable[], float pulsestrength, float threshold, unsigned int accumulator) +short calculatePulldown(float distancetable[], float topbit, float pulsestrength, float threshold, unsigned int accumulator) { - unsigned char bit[12]; + float bit[12]; for (unsigned int i = 0; i < 12; i++) { - bit[i] = (accumulator & (1u << i)) != 0 ? 1 : 0; + bit[i] = (accumulator & (1u << i)) != 0 ? 1.f : 0.f; } + bit[11] *= topbit; + float pulldown[12]; for (int sb = 0; sb < 12; sb++) @@ -117,7 +207,7 @@ short calculatePulldown(float distancetable[], float pulsestrength, float thresh if (cb == sb) continue; const float weight = distancetable[sb - cb + 12]; - avg += static_cast(1 - bit[cb]) * weight; + avg += (1.f - bit[cb]) * weight; n += weight; } @@ -131,7 +221,7 @@ short calculatePulldown(float distancetable[], float pulsestrength, float thresh for (unsigned int i = 0; i < 12; i++) { - const float bitValue = bit[i] != 0 ? 1.f - pulldown[i] : 0.f; + const float bitValue = bit[i] > 0.f ? 1.f - pulldown[i] : 0.f; if (bitValue > threshold) { value |= 1u << i; @@ -157,9 +247,26 @@ WaveformCalculator::WaveformCalculator() : } } -matrix_t* WaveformCalculator::buildPulldownTable(ChipModel model) +matrix_t* WaveformCalculator::buildPulldownTable(ChipModel model, CombinedWaveforms cws) { - const CombinedWaveformConfig* cfgArray = config[model == MOS6581 ? 0 : 1]; + std::lock_guard lock(PULLDOWN_CACHE_Lock); + + const int modelIdx = model == MOS6581 ? 0 : 1; + const CombinedWaveformConfig* cfgArray; + + switch (cws) + { + default: + case AVERAGE: + cfgArray = configAverage[modelIdx]; + break; + case WEAK: + cfgArray = configWeak[modelIdx]; + break; + case STRONG: + cfgArray = configStrong[modelIdx]; + break; + } cw_cache_t::iterator lb = PULLDOWN_CACHE.lower_bound(cfgArray); @@ -174,7 +281,7 @@ matrix_t* WaveformCalculator::buildPulldownTable(ChipModel model) { const CombinedWaveformConfig& cfg = cfgArray[wav]; - const distance_t distFunc = exponentialDistance; + const distance_t distFunc = cfg.distFunc; float distancetable[12 * 2 + 1]; distancetable[12] = 1.f; @@ -186,14 +293,11 @@ matrix_t* WaveformCalculator::buildPulldownTable(ChipModel model) for (unsigned int idx = 0; idx < (1u << 12); idx++) { - pdTable[wav][idx] = calculatePulldown(distancetable, cfg.pulsestrength, cfg.threshold, idx); + pdTable[wav][idx] = calculatePulldown(distancetable, cfg.topbit, cfg.pulsestrength, cfg.threshold, idx); } } -#ifdef HAVE_CXX11 + return &(PULLDOWN_CACHE.emplace_hint(lb, cw_cache_t::value_type(cfgArray, pdTable))->second); -#else - return &(PULLDOWN_CACHE.insert(lb, cw_cache_t::value_type(cfgArray, pdTable))->second); -#endif } } // namespace reSIDfp diff --git a/src/sound/resid-fp/WaveformCalculator.h b/src/sound/resid-fp/WaveformCalculator.h index 4ad6772745..f6db00c7d1 100644 --- a/src/sound/resid-fp/WaveformCalculator.h +++ b/src/sound/resid-fp/WaveformCalculator.h @@ -22,46 +22,33 @@ #ifndef WAVEFORMCALCULATOR_h #define WAVEFORMCALCULATOR_h -#include - #include "array.h" -#include "sidcxx11.h" + #include "siddefs-fp.h" namespace reSIDfp { -/** - * Combined waveform model parameters. - */ -typedef struct -{ - float threshold; - float pulsestrength; - float distance1; - float distance2; -} CombinedWaveformConfig; - /** * Combined waveform calculator for WaveformGenerator. * By combining waveforms, the bits of each waveform are effectively short - * circuited. A zero bit in one waveform will result in a zero output bit - * (thus the infamous claim that the waveforms are AND'ed). + * circuited, a zero bit in one waveform will result in a zero output bit, + * thus the claim that the waveforms are AND'ed. * However, a zero bit in one waveform may also affect the neighboring bits * in the output. * * Example: - * + * * 1 1 * Bit # 1 0 9 8 7 6 5 4 3 2 1 0 * ----------------------- * Sawtooth 0 0 0 1 1 1 1 1 1 0 0 0 - * + * * Triangle 0 0 1 1 1 1 1 1 0 0 0 0 - * + * * AND 0 0 0 1 1 1 1 1 0 0 0 0 - * + * * Output 0 0 0 0 1 1 1 0 0 0 0 0 * * @@ -98,14 +85,9 @@ typedef struct */ class WaveformCalculator { -private: - typedef std::map cw_cache_t; - private: matrix_t wftable; - cw_cache_t PULLDOWN_CACHE; - private: WaveformCalculator(); @@ -126,9 +108,10 @@ class WaveformCalculator * Build pulldown table for use by WaveformGenerator. * * @param model Chip model to use + * @param cws strength of combined waveforms * @return Pulldown table */ - matrix_t* buildPulldownTable(ChipModel model); + matrix_t* buildPulldownTable(ChipModel model, CombinedWaveforms cws); }; } // namespace reSIDfp diff --git a/src/sound/resid-fp/WaveformGenerator.cpp b/src/sound/resid-fp/WaveformGenerator.cpp index 4c7a55b3d0..be0738bbae 100644 --- a/src/sound/resid-fp/WaveformGenerator.cpp +++ b/src/sound/resid-fp/WaveformGenerator.cpp @@ -40,13 +40,13 @@ namespace reSIDfp * and [VICE Bug #1128](http://sourceforge.net/p/vice-emu/bugs/1128/) */ // ~95ms -const unsigned int FLOATING_OUTPUT_TTL_6581R3 = 54000; -const unsigned int FLOATING_OUTPUT_FADE_6581R3 = 1400; +constexpr unsigned int FLOATING_OUTPUT_TTL_6581R3 = 54000; +constexpr unsigned int FLOATING_OUTPUT_FADE_6581R3 = 1400; // ~1s -//const unsigned int FLOATING_OUTPUT_TTL_6581R4 = 1000000; +constexpr unsigned int FLOATING_OUTPUT_TTL_6581R4 = 1000000; // ~1s -const unsigned int FLOATING_OUTPUT_TTL_8580R5 = 800000; -const unsigned int FLOATING_OUTPUT_FADE_8580R5 = 50000; +constexpr unsigned int FLOATING_OUTPUT_TTL_8580R5 = 800000; +constexpr unsigned int FLOATING_OUTPUT_FADE_8580R5 = 50000; /** * Number of cycles after which the shift register is reset @@ -58,15 +58,15 @@ const unsigned int FLOATING_OUTPUT_FADE_8580R5 = 50000; * only the big difference between the old and new models. */ // ~210ms -const unsigned int SHIFT_REGISTER_RESET_6581R3 = 50000; -const unsigned int SHIFT_REGISTER_FADE_6581R3 = 15000; +constexpr unsigned int SHIFT_REGISTER_RESET_6581R3 = 50000; +constexpr unsigned int SHIFT_REGISTER_FADE_6581R3 = 15000; // ~2.15s -//const unsigned int SHIFT_REGISTER_RESET_6581R4 = 2150000; +constexpr unsigned int SHIFT_REGISTER_RESET_6581R4 = 2150000; // ~2.8s -const unsigned int SHIFT_REGISTER_RESET_8580R5 = 986000; -const unsigned int SHIFT_REGISTER_FADE_8580R5 = 314300; +constexpr unsigned int SHIFT_REGISTER_RESET_8580R5 = 986000; +constexpr unsigned int SHIFT_REGISTER_FADE_8580R5 = 314300; -const unsigned int shift_mask = +constexpr unsigned int shift_mask = ~( (1u << 2) | // Bit 20 (1u << 4) | // Bit 18 @@ -107,15 +107,100 @@ const unsigned int shift_mask = * -----+-------+--------------+-------------- * phi1 | 1 | X --> X | A --> A <- shift phase 2 * phi2 | 1 | X <-> X | A <-> A + * + * + * Normal cycles + * ------------- + * Normally, when noise is selected along with another waveform, + * c1 and c2 are closed and the output bits pull down the corresponding + * shift register bits. + * + * noi_out_x noi_out_x+1 + * ^ ^ + * | | + * +-------------+ +-------------+ + * | | | | + * +---o<|---+ | +---o<|---+ | + * | | | | | | + * c2 | c1 | | c2 | c1 | | + * | | | | | | + * >---/---+---|>o---+ +---/---+---|>o---+ +---/---> + * LC LC LC + * + * + * Shift phase 1 + * ------------- + * During shift phase 1 c1 and c2 are open, the SR bits are floating + * and will be driven by the output of combined waveforms, + * or slowly turn high. + * + * noi_out_x noi_out_x+1 + * ^ ^ + * | | + * +-------------+ +-------------+ + * | | | | + * +---o<|---+ | +---o<|---+ | + * | | | | | | + * c2 / c1 / | c2 / c1 / | + * | | | | | | + * >-------+---|>o---+ +-------+---|>o---+ +-------> + * LC LC LC + * + * + * Shift phase 2 (phi1) + * -------------------- + * During the first half cycle of shift phase 2 c1 is closed + * so the value from of noi_out_x-1 enters the bit. + * + * noi_out_x noi_out_x+1 + * ^ ^ + * | | + * +-------------+ +-------------+ + * | | | | + * +---o<|---+ | +---o<|---+ | + * | | | | | | + * c2 / c1 | | c2 / c1 | | + * | | | | | | + * >---/---+---|>o---+ +---/---+---|>o---+ +---/---> + * LC LC LC + * + * + * Shift phase 2 (phi2) + * -------------------- + * On the second half of shift phase 2 c2 closes and + * we're back to normal cycles. */ inline bool do_writeback(unsigned int waveform_old, unsigned int waveform_new, bool is6581) { // no writeback without combined waveforms - if (waveform_new <= 8) - return false; + if (waveform_old <= 8) - return false; // fixes SID/noisewriteback/noise_writeback_test2-{old,new} + // fixes SID/noisewriteback/noise_writeback_test2-{old,new} + return false; + + if (waveform_new < 8) + return false; + + if ((waveform_new == 8) + // breaks noise_writeback_check_F_to_8_old + // but fixes simple and scan + && (waveform_old != 0xf)) + { + // fixes + // noise_writeback_check_9_to_8_old + // noise_writeback_check_A_to_8_old + // noise_writeback_check_B_to_8_old + // noise_writeback_check_D_to_8_old + // noise_writeback_check_E_to_8_old + // noise_writeback_check_F_to_8_old + // noise_writeback_check_9_to_8_new + // noise_writeback_check_A_to_8_new + // noise_writeback_check_D_to_8_new + // noise_writeback_check_E_to_8_new + // noise_writeback_test1-{old,new} + return false; + } // What's happening here? if (is6581 && @@ -190,8 +275,16 @@ void WaveformGenerator::write_shift_register() { if (unlikely(waveform > 0x8)) { +#if 0 + // FIXME this breaks SID/wf12nsr/wf12nsr if (waveform == 0xc) - return; // breaks SID/wf12nsr/wf12nsr + // fixes + // noise_writeback_check_8_to_C_old + // noise_writeback_check_9_to_C_old + // noise_writeback_check_A_to_C_old + // noise_writeback_check_C_to_C_old + return; +#endif // Write changes to the shift register output caused by combined waveforms // back into the shift register. diff --git a/src/sound/resid-fp/WaveformGenerator.h b/src/sound/resid-fp/WaveformGenerator.h index adca6c2281..7bbccbc808 100644 --- a/src/sound/resid-fp/WaveformGenerator.h +++ b/src/sound/resid-fp/WaveformGenerator.h @@ -93,64 +93,64 @@ namespace reSIDfp class WaveformGenerator { private: - matrix_t* model_wave; - matrix_t* model_pulldown; + matrix_t* model_wave = nullptr; + matrix_t* model_pulldown = nullptr; - short* wave; - short* pulldown; + short* wave = nullptr; + short* pulldown = nullptr; // PWout = (PWn/40.95)% - unsigned int pw; + unsigned int pw = 0; - unsigned int shift_register; + unsigned int shift_register = 0; /// Shift register is latched when transitioning to shift phase 1. - unsigned int shift_latch; + unsigned int shift_latch = 0; /// Emulation of pipeline causing bit 19 to clock the shift register. - int shift_pipeline; + int shift_pipeline = 0; - unsigned int ring_msb_mask; - unsigned int no_noise; - unsigned int noise_output; - unsigned int no_noise_or_noise_output; - unsigned int no_pulse; - unsigned int pulse_output; + unsigned int ring_msb_mask = 0; + unsigned int no_noise = 0; + unsigned int noise_output = 0; + unsigned int no_noise_or_noise_output = 0; + unsigned int no_pulse = 0; + unsigned int pulse_output = 0; /// The control register right-shifted 4 bits; used for output function table lookup. - unsigned int waveform; + unsigned int waveform = 0; - unsigned int waveform_output; + unsigned int waveform_output = 0; /// Current accumulator value. - unsigned int accumulator; + unsigned int accumulator = 0x555555; // Accumulator's even bits are high on powerup // Fout = (Fn*Fclk/16777216)Hz - unsigned int freq; + unsigned int freq = 0; /// 8580 tri/saw pipeline - unsigned int tri_saw_pipeline; + unsigned int tri_saw_pipeline = 0x555; /// The OSC3 value - unsigned int osc3; + unsigned int osc3 = 0; /// Remaining time to fully reset shift register. - unsigned int shift_register_reset; + unsigned int shift_register_reset = 0; // The wave signal TTL when no waveform is selected. - unsigned int floating_output_ttl; + unsigned int floating_output_ttl = 0; /// The control register bits. Gate is handled by EnvelopeGenerator. //@{ - bool test; - bool sync; + bool test = false; + bool sync = false; //@} /// Test bit is latched at phi2 for the noise XOR. bool test_or_reset; /// Tell whether the accumulator MSB was set high on this cycle. - bool msb_rising; + bool msb_rising = false; bool is6581; //-V730_NOINIT this is initialized in the SID constructor @@ -160,7 +160,7 @@ class WaveformGenerator void write_shift_register(); void set_noise_output(); - + void set_no_noise_or_noise_output(); void waveBitfade(); @@ -194,35 +194,6 @@ class WaveformGenerator */ void synchronize(WaveformGenerator* syncDest, const WaveformGenerator* syncSource) const; - /** - * Constructor. - */ - WaveformGenerator() : - model_wave(nullptr), - model_pulldown(nullptr), - wave(nullptr), - pulldown(nullptr), - pw(0), - shift_register(0), - shift_pipeline(0), - ring_msb_mask(0), - no_noise(0), - noise_output(0), - no_noise_or_noise_output(0), - no_pulse(0), - pulse_output(0), - waveform(0), - waveform_output(0), - accumulator(0x555555), // Accumulator's even bits are high on powerup - freq(0), - tri_saw_pipeline(0x555), - osc3(0), - shift_register_reset(0), - floating_output_ttl(0), - test(false), - sync(false), - msb_rising(false) {} - /** * Write FREQ LO register. * @@ -397,13 +368,13 @@ unsigned int WaveformGenerator::output(const WaveformGenerator* ringModulator) { osc3 = waveform_output; } - // In the 6581 the top bit of the accumulator may be driven low by combined waveforms // when the sawtooth is selected - if (is6581 - && (waveform & 0x2) - && ((waveform_output & 0x800) == 0)) + if (is6581 && (waveform & 0x2) && ((waveform_output & 0x800) == 0)) + { + msb_rising = 0; accumulator &= 0x7fffff; + } write_shift_register(); } diff --git a/src/sound/resid-fp/array.h b/src/sound/resid-fp/array.h index a0d3909662..58c4617aee 100644 --- a/src/sound/resid-fp/array.h +++ b/src/sound/resid-fp/array.h @@ -26,9 +26,7 @@ # include "config.h" #endif -#ifdef HAVE_CXX11 -# include -#endif +#include /** * Counter. @@ -36,11 +34,7 @@ class counter { private: -#ifndef HAVE_CXX11 - volatile unsigned int c; -#else std::atomic c; -#endif public: counter() : c(1) {} @@ -81,6 +75,6 @@ class matrix T const* operator[](unsigned int a) const { return &data[a * y]; } }; -typedef matrix matrix_t; +using matrix_t = matrix; #endif diff --git a/src/sound/resid-fp/config.h b/src/sound/resid-fp/config.h index 0eeba8dee8..399003a55a 100644 --- a/src/sound/resid-fp/config.h +++ b/src/sound/resid-fp/config.h @@ -1 +1 @@ -#define HAVE_CXX14 +#define HAVE_CXX17 diff --git a/src/sound/resid-fp/resample/Resampler.h b/src/sound/resid-fp/resample/Resampler.h index 904f65458e..293fda6ce2 100644 --- a/src/sound/resid-fp/resample/Resampler.h +++ b/src/sound/resid-fp/resample/Resampler.h @@ -1,7 +1,7 @@ /* * This file is part of libsidplayfp, a SID player engine. * - * Copyright 2011-2020 Leandro Nini + * Copyright 2011-2024 Leandro Nini * Copyright 2007-2010 Antti Lankila * * This program is free software; you can redistribute it and/or modify @@ -23,6 +23,7 @@ #define RESAMPLER_H #include +#include #include "../sidcxx11.h" @@ -37,28 +38,45 @@ namespace reSIDfp */ class Resampler { -protected: - inline short softClip(int x) const +private: + template + static inline int clipper(int x) { + assert(x >= 0); constexpr int threshold = 28000; if (likely(x < threshold)) return x; - constexpr double t = threshold / 32768.; + constexpr double max_val = static_cast(m); + constexpr double t = threshold / max_val; constexpr double a = 1. - t; constexpr double b = 1. / a; - double value = static_cast(x - threshold) / 32768.; - value = t + a * tanh(b * value); - return static_cast(value * 32768.); + double value = static_cast(x - threshold) / max_val; + value = t + a * std::tanh(b * value); + return static_cast(value * max_val); + } + + /* + * Soft Clipping implementation, splitted for test. + */ + static inline int softClipImpl(int x) + { + return x < 0 ? -clipper<32768>(-x) : clipper<32767>(x); } +protected: + /* + * Soft Clipping into 16 bit range [-32768,32767] + */ + static inline short softClip(int x) { return static_cast(softClipImpl(x)); } + virtual int output() const = 0; Resampler() {} public: - virtual ~Resampler() {} + virtual ~Resampler() = default; /** * Input a sample into resampler. Output "true" when resampler is ready with new sample. @@ -73,9 +91,10 @@ class Resampler * * @return resampled sample */ - short getOutput() const + inline short getOutput(int scaleFactor) const { - return softClip(output()); + const int out = (scaleFactor * output()) / 2; + return softClip(out); } virtual void reset() = 0; diff --git a/src/sound/resid-fp/resample/SincResampler.cpp b/src/sound/resid-fp/resample/SincResampler.cpp index df7d8af833..14ae137524 100644 --- a/src/sound/resid-fp/resample/SincResampler.cpp +++ b/src/sound/resid-fp/resample/SincResampler.cpp @@ -1,7 +1,7 @@ /* * This file is part of libsidplayfp, a SID player engine. * - * Copyright 2011-2020 Leandro Nini + * Copyright 2011-2024 Leandro Nini * Copyright 2007-2010 Antti Lankila * Copyright 2004 Dag Lem * @@ -22,11 +22,16 @@ #include "SincResampler.h" +#ifdef HAVE_CXX20 +# include +#endif + +#include +#include #include #include #include -#include -#include +#include #include "../siddefs-fp.h" @@ -34,10 +39,8 @@ # include "config.h" #endif -#ifdef HAVE_EMMINTRIN_H -# include -#elif defined HAVE_MMINTRIN_H -# include +#ifdef HAVE_SMMINTRIN_H +# include #elif defined(HAVE_ARM_NEON_H) # include #endif @@ -45,15 +48,10 @@ namespace reSIDfp { -typedef std::map fir_cache_t; - -/// Cache for the expensive FIR table computation results. -fir_cache_t FIR_CACHE; - /// Maximum error acceptable in I0 is 1e-6, or ~96 dB. -const double I0E = 1e-6; +constexpr double I0E = 1e-6; -const int BITS = 16; +constexpr int BITS = 16; /** * Compute the 0th order modified Bessel function of the first kind. @@ -90,7 +88,7 @@ double I0(double x) * @param bLength length of the sinc buffer * @return convolved result */ -int convolve(const short* a, const short* b, int bLength) +int convolve(const int* a, const short* b, int bLength) { #ifdef HAVE_EMMINTRIN_H int out = 0; @@ -102,7 +100,7 @@ int convolve(const short* a, const short* b, int bLength) { if (offset) { - const int l = (0x10 - offset)/2; + const int l = (0x10 - offset) / 2; for (int i = 0; i < l; i++) { @@ -208,9 +206,9 @@ int convolve(const short* a, const short* b, int bLength) bLength &= 3; #else int32x4_t acc = vdupq_n_s32(0); - + const int n = bLength / 4; - + for (int i = 0; i < n; i++) { const int16x4_t h_vec = vld1_s16(a); @@ -219,12 +217,12 @@ int convolve(const short* a, const short* b, int bLength) a += 4; b += 4; } - + int out = vgetq_lane_s32(acc, 0) + vgetq_lane_s32(acc, 1) + vgetq_lane_s32(acc, 2) + vgetq_lane_s32(acc, 3); - + bLength &= 3; #endif #else @@ -233,7 +231,7 @@ int convolve(const short* a, const short* b, int bLength) for (int i = 0; i < bLength; i++) { - out += *a++ * *b++; + out += a[i] * static_cast(b[i]); } return (out + (1 << 14)) >> 15; @@ -265,17 +263,27 @@ int SincResampler::fir(int subcycle) return v1 + (firTableOffset * (v2 - v1) >> 10); } -SincResampler::SincResampler(double clockFrequency, double samplingFrequency, double highestAccurateFrequency) : - sampleIndex(0), - cyclesPerSample(static_cast(clockFrequency / samplingFrequency * 1024.)), - sampleOffset(0), - outputValue(0) +SincResampler::SincResampler( + double clockFrequency, + double samplingFrequency, + double highestAccurateFrequency) : + cyclesPerSample(static_cast(clockFrequency / samplingFrequency * 1024.)) { +#if defined(HAVE_CXX20) && defined(__cpp_lib_constexpr_cmath) + constexpr double PI = std::numbers::pi; +#else +# ifdef M_PI + constexpr double PI = M_PI; +#else + constexpr double PI = 3.14159265358979323846; +# endif +#endif + // 16 bits -> -96dB stopband attenuation. - const double A = -20. * log10(1.0 / (1 << BITS)); + const double A = -20. * std::log10(1.0 / (1 << BITS)); // A fraction of the bandwidth is allocated to the transition band, which we double // because we design the filter to transition halfway at nyquist. - const double dw = (1. - 2.*highestAccurateFrequency / samplingFrequency) * M_PI * 2.; + const double dw = (1. - 2.*highestAccurateFrequency / samplingFrequency) * PI * 2.; // For calculation of beta and N see the reference for the kaiserord // function in the MATLAB Signal Processing Toolbox: @@ -283,6 +291,7 @@ SincResampler::SincResampler(double clockFrequency, double samplingFrequency, do const double beta = 0.1102 * (A - 8.7); const double I0beta = I0(beta); const double cyclesPerSampleD = clockFrequency / samplingFrequency; + const double inv_cyclesPerSampleD = samplingFrequency / clockFrequency; { // The filter order will maximally be 124 with the current constraints. @@ -302,40 +311,22 @@ SincResampler::SincResampler(double clockFrequency, double samplingFrequency, do assert(firN < RINGSIZE); // Error is bounded by err < 1.234 / L^2, so L = sqrt(1.234 / (2^-16)) = sqrt(1.234 * 2^16). - firRES = static_cast(ceil(sqrt(1.234 * (1 << BITS)) / cyclesPerSampleD)); + firRES = static_cast(std::ceil(std::sqrt(1.234 * (1 << BITS)) * inv_cyclesPerSampleD)); // firN*firRES represent the total resolution of the sinc sampling. JOS // recommends a length of 2^BITS, but we don't quite use that good a filter. // The filter test program indicates that the filter performs well, though. } - // Create the map key - std::ostringstream o; - o << firN << "," << firRES << "," << cyclesPerSampleD; - const std::string firKey = o.str(); - fir_cache_t::iterator lb = FIR_CACHE.lower_bound(firKey); - - // The FIR computation is expensive and we set sampling parameters often, but - // from a very small set of choices. Thus, caching is used to speed initialization. - if (lb != FIR_CACHE.end() && !(FIR_CACHE.key_comp()(firKey, lb->first))) - { - firTable = &(lb->second); - } - else { // Allocate memory for FIR tables. - matrix_t tempTable(firRES, firN); -#ifdef HAVE_CXX11 - firTable = &(FIR_CACHE.emplace_hint(lb, fir_cache_t::value_type(firKey, tempTable))->second); -#else - firTable = &(FIR_CACHE.insert(lb, fir_cache_t::value_type(firKey, tempTable))->second); -#endif + firTable = new matrix_t(firRES, firN); // The cutoff frequency is midway through the transition band, in effect the same as nyquist. - const double wc = M_PI; + const double wc = PI; // Calculate the sinc tables. - const double scale = 32768.0 * wc / cyclesPerSampleD / M_PI; + const double scale = 32768.0 * wc * inv_cyclesPerSampleD / PI; // we're not interested in the fractional part // so use int division before converting to double @@ -351,10 +342,10 @@ SincResampler::SincResampler(double clockFrequency, double samplingFrequency, do const double x = j - jPhase; const double xt = x / firN_2; - const double kaiserXt = fabs(xt) < 1. ? I0(beta * sqrt(1. - xt * xt)) / I0beta : 0.; + const double kaiserXt = std::fabs(xt) < 1. ? I0(beta * std::sqrt(1. - xt * xt)) / I0beta : 0.; - const double wt = wc * x / cyclesPerSampleD; - const double sincWt = fabs(wt) >= 1e-8 ? sin(wt) / wt : 1.; + const double wt = wc * x * inv_cyclesPerSampleD; + const double sincWt = std::fabs(wt) >= 1e-8 ? std::sin(wt) / wt : 1.; (*firTable)[i][j] = static_cast(scale * sincWt * kaiserXt); } @@ -362,18 +353,16 @@ SincResampler::SincResampler(double clockFrequency, double samplingFrequency, do } } +SincResampler::~SincResampler() +{ + delete firTable; +} + bool SincResampler::input(int input) { bool ready = false; - /* - * Clip the input as it may overflow the 16 bit range. - * - * Approximate measured input ranges: - * 6581: [-24262,+25080] (Kawasaki_Synthesizer_Demo) - * 8580: [-21514,+35232] (64_Forever, Drum_Fool) - */ - sample[sampleIndex] = sample[sampleIndex + RINGSIZE] = softClip(input); + sample[sampleIndex] = sample[sampleIndex + RINGSIZE] = input; sampleIndex = (sampleIndex + 1) & (RINGSIZE - 1); if (sampleOffset < 1024) @@ -390,7 +379,7 @@ bool SincResampler::input(int input) void SincResampler::reset() { - memset(sample, 0, sizeof(sample)); + std::fill(std::begin(sample), std::end(sample), 0); sampleOffset = 0; } diff --git a/src/sound/resid-fp/resample/SincResampler.h b/src/sound/resid-fp/resample/SincResampler.h index 7502d96fda..c3228171f4 100644 --- a/src/sound/resid-fp/resample/SincResampler.h +++ b/src/sound/resid-fp/resample/SincResampler.h @@ -1,7 +1,7 @@ /* * This file is part of libsidplayfp, a SID player engine. * - * Copyright 2011-2013 Leandro Nini + * Copyright 2011-2024 Leandro Nini * Copyright 2007-2010 Antti Lankila * Copyright 2004 Dag Lem * @@ -25,13 +25,8 @@ #include "Resampler.h" -#include -#include - #include "../array.h" -#include "../sidcxx11.h" - namespace reSIDfp { @@ -54,13 +49,13 @@ class SincResampler final : public Resampler { private: /// Size of the ring buffer, must be a power of 2 - static const int RINGSIZE = 2048; + static constexpr int RINGSIZE = 2048; private: /// Table of the fir filter coefficients matrix_t* firTable; - int sampleIndex; + int sampleIndex = 0; /// Filter resolution int firRES; @@ -70,11 +65,11 @@ class SincResampler final : public Resampler const int cyclesPerSample; - int sampleOffset; + int sampleOffset = 0; - int outputValue; + int outputValue = 0; - short sample[RINGSIZE * 2]; + int sample[RINGSIZE * 2]; private: int fir(int subcycle); @@ -82,25 +77,25 @@ class SincResampler final : public Resampler public: /** * Use a clock freqency of 985248Hz for PAL C64, 1022730Hz for NTSC C64. - * The default end of passband frequency is pass_freq = 0.9*sample_freq/2 - * for sample frequencies up to ~ 44.1kHz, and 20kHz for higher sample frequencies. * - * For resampling, the ratio between the clock frequency and the sample frequency - * is limited as follows: 125*clock_freq/sample_freq < 16384 + * For resampling, the ratio between the clock frequency + * and the sample frequency is limited as follows: + * 125*clock_freq/sample_freq < 16384 + * * E.g. provided a clock frequency of ~ 1MHz, the sample frequency * can not be set lower than ~ 8kHz. - * A lower sample frequency would make the resampling code overfill its 16k sample ring buffer. - * - * The end of passband frequency is also limited: pass_freq <= 0.9*sample_freq/2 - * - * E.g. for a 44.1kHz sampling rate the end of passband frequency is limited - * to slightly below 20kHz. This constraint ensures that the FIR table is not overfilled. + * A lower sample frequency would make the resampling code overfill + * its 16k sample ring buffer. * * @param clockFrequency System clock frequency at Hz * @param samplingFrequency Desired output sampling rate - * @param highestAccurateFrequency + * @param highestAccurateFrequency passband frequency limit */ - SincResampler(double clockFrequency, double samplingFrequency, double highestAccurateFrequency); + SincResampler( + double clockFrequency, + double samplingFrequency, + double highestAccurateFrequency); + ~SincResampler() override; bool input(int input) override; diff --git a/src/sound/resid-fp/resample/TwoPassSincResampler.h b/src/sound/resid-fp/resample/TwoPassSincResampler.h index 81659193a7..7ba28ea8ed 100644 --- a/src/sound/resid-fp/resample/TwoPassSincResampler.h +++ b/src/sound/resid-fp/resample/TwoPassSincResampler.h @@ -51,14 +51,25 @@ class TwoPassSincResampler final : public Resampler public: // Named constructor - static TwoPassSincResampler* create(double clockFrequency, double samplingFrequency, double highestAccurateFrequency) + static TwoPassSincResampler* create(double clockFrequency, double samplingFrequency) { - // Calculation according to Laurent Ganier. It evaluates to about 120 kHz at typical settings. + // Set the passband frequency slightly below half sampling frequency + // pass_freq <= 0.9*sample_freq/2 + // + // This constraint ensures that the FIR table is not overfilled. + // For higher sampling frequencies we're fine with 20KHz + const double halfFreq = (samplingFrequency > 44000.) + ? 20000. : samplingFrequency * 0.45; + + // Calculation according to Laurent Ganier. + // It evaluates to about 120 kHz at typical settings. // Some testing around the chosen value seems to confirm that this does work. - double const intermediateFrequency = 2. * highestAccurateFrequency - + sqrt(2. * highestAccurateFrequency * clockFrequency - * (samplingFrequency - 2. * highestAccurateFrequency) / samplingFrequency); - return new TwoPassSincResampler(clockFrequency, samplingFrequency, highestAccurateFrequency, intermediateFrequency); + double const intermediateFrequency = 2. * halfFreq + + std::sqrt(2. * halfFreq * clockFrequency + * (samplingFrequency - 2. * halfFreq) / samplingFrequency); + + return new TwoPassSincResampler( + clockFrequency, samplingFrequency, halfFreq, intermediateFrequency); } bool input(int sample) override diff --git a/src/sound/resid-fp/resample/test.cpp b/src/sound/resid-fp/resample/test.cpp index b229e9e4dc..d84e641d25 100644 --- a/src/sound/resid-fp/resample/test.cpp +++ b/src/sound/resid-fp/resample/test.cpp @@ -35,6 +35,10 @@ # define unique_ptr auto_ptr #endif +#ifndef M_PI +# define M_PI 3.14159265358979323846 +#endif + /** * Simple sin waveform in, power output measurement function. * It would be far better to use FFT. @@ -57,7 +61,7 @@ int main(int, const char*[]) for (int j = 0; j < RINGSIZE; j ++) { - int signal = static_cast(32768.0 * sin(k++ * omega) * sqrt(2)); + int signal = static_cast(32768.0 * std::sin(k++ * omega) * sqrt(2)); r->input(signal); } @@ -67,7 +71,7 @@ int main(int, const char*[]) /* Now, during measurement stage, put 100 cycles of waveform through filter. */ for (int j = 0; j < 100000; j ++) { - int signal = static_cast(32768.0 * sin(k++ * omega) * sqrt(2)); + int signal = static_cast(32768.0 * std::sin(k++ * omega) * std::sqrt(2)); if (r->input(signal)) { @@ -77,7 +81,7 @@ int main(int, const char*[]) } } - results.insert(std::make_pair(freq, 10 * log10(pwr / n))); + results.insert(std::make_pair(freq, 10 * std::log10(pwr / n))); } clock_t end = clock(); diff --git a/src/sound/resid-fp/sid.h b/src/sound/resid-fp/sid.h index 05ad83c3b6..ef3dc71b3d 100644 --- a/src/sound/resid-fp/sid.h +++ b/src/sound/resid-fp/sid.h @@ -1,7 +1,7 @@ /* * This file is part of libsidplayfp, a SID player engine. * - * Copyright 2011-2016 Leandro Nini + * Copyright 2011-2024 Leandro Nini * Copyright 2007-2010 Antti Lankila * Copyright 2004 Dag Lem * @@ -26,6 +26,9 @@ #include #include "siddefs-fp.h" +#include "ExternalFilter.h" +#include "Potentiometer.h" +#include "Voice.h" #include "sidcxx11.h" @@ -35,9 +38,6 @@ namespace reSIDfp class Filter; class Filter6581; class Filter8580; -class ExternalFilter; -class Potentiometer; -class Voice; class Resampler; /** @@ -64,28 +64,31 @@ class SID Filter* filter; /// Filter used, if model is set to 6581 - std::unique_ptr const filter6581; + Filter6581* const filter6581; /// Filter used, if model is set to 8580 - std::unique_ptr const filter8580; + Filter8580* const filter8580; + + /// Resampler used by audio generation code. + std::unique_ptr resampler; /** * External filter that provides high-pass and low-pass filtering * to adjust sound tone slightly. */ - std::unique_ptr const externalFilter; - - /// Resampler used by audio generation code. - std::unique_ptr resampler; + ExternalFilter externalFilter; /// Paddle X register support - std::unique_ptr const potX; + Potentiometer potX; /// Paddle Y register support - std::unique_ptr const potY; + Potentiometer potY; /// SID voices - std::unique_ptr voice[3]; + Voice voice[3]; + + /// Used to amplify the output by x/2 to get an adequate playback volume + int scaleFactor; /// Time to live for the last written value int busValueTtl; @@ -99,12 +102,12 @@ class SID /// Currently active chip model. ChipModel model; + /// Currently selected combined waveforms strength. + CombinedWaveforms cws; + /// Last written value unsigned char busValue; - /// Flags for muted channels - bool muted[3]; - /** * Emulated nonlinearity of the envelope DAC. * @@ -132,7 +135,7 @@ class SID * * @return the output sample */ - int output() const; + int output(); /** * Calculate the numebr of cycles according to current parameters @@ -159,6 +162,14 @@ class SID */ ChipModel getChipModel() const { return model; } + /** + * Set combined waveforms strength. + * + * @param cws strength of combined waveforms + * @throw SIDError + */ + void setCombinedWaveforms(CombinedWaveforms cws); + /** * SID reset. */ @@ -204,14 +215,6 @@ class SID */ void write(int offset, unsigned char value); - /** - * SID voice muting. - * - * @param channel channel to modify - * @param enable is muted? - */ - void mute(int channel, bool enable) { muted[channel] = enable; } - /** * Setting of SID sampling parameters. * @@ -237,7 +240,11 @@ class SID * @param highestAccurateFrequency * @throw SIDError */ - void setSamplingParameters(double clockFrequency, SamplingMethod method, double samplingFrequency, double highestAccurateFrequency); + void setSamplingParameters( + double clockFrequency, + SamplingMethod method, + double samplingFrequency + ); /** * Clock SID forward using chosen output sampling algorithm. @@ -267,6 +274,13 @@ class SID */ void setFilter6581Curve(double filterCurve); + /** + * Set filter range parameter for 6581 model + * + * @see Filter6581::setFilterRange(double) + */ + void setFilter6581Range ( double adjustment ); + /** * Set filter curve parameter for 8580 model. * @@ -312,13 +326,22 @@ void SID::ageBusValue(unsigned int n) } RESID_INLINE -int SID::output() const +int SID::output() { - const int v1 = voice[0]->output(voice[2]->wave()); - const int v2 = voice[1]->output(voice[0]->wave()); - const int v3 = voice[2]->output(voice[1]->wave()); + const float o1 = voice[0].output(voice[2].wave()); + const float o2 = voice[1].output(voice[0].wave()); + const float o3 = voice[2].output(voice[1].wave()); + + const unsigned int env1 = voice[0].envelope()->output(); + const unsigned int env2 = voice[1].envelope()->output(); + const unsigned int env3 = voice[2].envelope()->output(); + + const int v1 = filter->getNormalizedVoice(o1, env1); + const int v2 = filter->getNormalizedVoice(o2, env2); + const int v3 = filter->getNormalizedVoice(o3, env3); - return externalFilter->clock(filter->clock(v1, v2, v3)); + const int input = static_cast(filter->clock(v1, v2, v3)); + return externalFilter.clock(input); } @@ -337,18 +360,18 @@ int SID::clock(unsigned int cycles, short* buf) for (unsigned int i = 0; i < delta_t; i++) { // clock waveform generators - voice[0]->wave()->clock(); - voice[1]->wave()->clock(); - voice[2]->wave()->clock(); + voice[0].wave()->clock(); + voice[1].wave()->clock(); + voice[2].wave()->clock(); // clock envelope generators - voice[0]->envelope()->clock(); - voice[1]->envelope()->clock(); - voice[2]->envelope()->clock(); + voice[0].envelope()->clock(); + voice[1].envelope()->clock(); + voice[2].envelope()->clock(); if (unlikely(resampler->input(output()))) { - buf[s++] = resampler->getOutput(); + buf[s++] = resampler->getOutput(scaleFactor); } } diff --git a/src/sound/resid-fp/siddefs-fp.h b/src/sound/resid-fp/siddefs-fp.h index 7061e3a85d..9411b1694c 100644 --- a/src/sound/resid-fp/siddefs-fp.h +++ b/src/sound/resid-fp/siddefs-fp.h @@ -26,10 +26,6 @@ // Compiler specifics. #define HAVE_BUILTIN_EXPECT true -#ifndef M_PI -# define M_PI 3.14159265358979323846 -#endif - // Branch prediction macros, lifted off the Linux kernel. #if RESID_BRANCH_HINTS && HAVE_BUILTIN_EXPECT # define likely(x) __builtin_expect(!!(x), 1) @@ -43,6 +39,8 @@ namespace reSIDfp { typedef enum { MOS6581=1, MOS8580 } ChipModel; +typedef enum { AVERAGE=1, WEAK, STRONG } CombinedWaveforms; + typedef enum { DECIMATE=1, RESAMPLE } SamplingMethod; } diff --git a/src/sound/resid-fp/siddefs-fp.h.in b/src/sound/resid-fp/siddefs-fp.h.in index 4c31ffb461..dfe543db5d 100644 --- a/src/sound/resid-fp/siddefs-fp.h.in +++ b/src/sound/resid-fp/siddefs-fp.h.in @@ -26,10 +26,6 @@ // Compiler specifics. #define HAVE_BUILTIN_EXPECT @HAVE_BUILTIN_EXPECT@ -#ifndef M_PI -# define M_PI 3.14159265358979323846 -#endif - // Branch prediction macros, lifted off the Linux kernel. #if RESID_BRANCH_HINTS && HAVE_BUILTIN_EXPECT # define likely(x) __builtin_expect(!!(x), 1) @@ -43,6 +39,8 @@ namespace reSIDfp { typedef enum { MOS6581=1, MOS8580 } ChipModel; +typedef enum { AVERAGE=1, WEAK, STRONG } CombinedWaveforms; + typedef enum { DECIMATE=1, RESAMPLE } SamplingMethod; } diff --git a/src/sound/snd_opl.c b/src/sound/snd_opl.c index d98b3ccc2e..cddc181194 100644 --- a/src/sound/snd_opl.c +++ b/src/sound/snd_opl.c @@ -58,7 +58,12 @@ fm_driver_get(int chip_id, fm_drv_t *drv) drv->priv = device_add_inst(&ymf262_ymfm_device, fm_dev_inst[fm_driver][chip_id]++); } break; - +#ifdef USE_LIBSERIALPORT + case FM_OPL2BOARD: + *drv = ymfm_opl2board_drv; + drv->priv = device_add_inst(&ym_opl2board_device, fm_dev_inst[fm_driver][chip_id]++); + break; +#endif case FM_YMF289B: *drv = ymfm_drv; drv->priv = device_add_inst(&ymf289b_ymfm_device, fm_dev_inst[fm_driver][chip_id]++); diff --git a/src/sound/snd_opl2board.c b/src/sound/snd_opl2board.c new file mode 100644 index 0000000000..efd408b970 --- /dev/null +++ b/src/sound/snd_opl2board.c @@ -0,0 +1,194 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * Interface to the OPL2Board External audio device (USB) + * + * + * Authors: Jose Phillips + * Fred N. van Kempen, + * Miran Grca, + * + * Copyright 2024 Jose Phillips. + * Copyright 2017-2020 Fred N. van Kempen. + * Copyright 2016-2020 Miran Grca. + */ + +#include +#include +#include +#include +#include +#include +#define HAVE_STDARG_H + +#include <86box/86box.h> +#include <86box/device.h> +#include <86box/io.h> +#include <86box/mca.h> +#include <86box/sound.h> +#include <86box/timer.h> +#include <86box/snd_opl.h> +#include <86box/plat_unused.h> + + +#ifdef ENABLE_OPL2DEVICE_LOG +int opl2board_device_do_log = ENABLE_OPL2DEVICE_LOG; + +static void +opl2board_device_log(const char *fmt, ...) +{ + va_list ap; + + if (opl2board_device_do_log) { + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); + } +} +#else +# define opl2board_device_log(fmt, ...) +#endif + +typedef struct opl2board_device_t { + fm_drv_t opl; + uint8_t pos_regs[8]; +} opl2board_device_t; + +static void +opl2board_device_get_buffer(int32_t *buffer, int len, void *priv) +{ + opl2board_device_t *serial = (opl2board_device_t *) priv; + + const int32_t *opl_buf = serial->opl.update(serial->opl.priv); + + for (int c = 0; c < len * 2; c++) + buffer[c] += opl_buf[c]; + + serial->opl.reset_buffer(serial->opl.priv); +} + +uint8_t +opl2board_device_mca_read(int port, void *priv) +{ + const opl2board_device_t *serial = (opl2board_device_t *) priv; + + opl2board_device_log("opl2board_device_mca_read: port=%04x\n", port); + + return serial->pos_regs[port & 7]; +} + +void +opl2board_device_mca_write(int port, uint8_t val, void *priv) +{ + opl2board_device_t *serial = (opl2board_device_t *) priv; + + if (port < 0x102) + return; + + opl2board_device_log("opl2board_device_mca_write: port=%04x val=%02x\n", port, val); + + switch (port) { + case 0x102: + if ((serial->pos_regs[2] & 1) && !(val & 1)) + io_removehandler(0x0388, 0x0002, + serial->opl.read, NULL, NULL, + serial->opl.write, NULL, NULL, + serial->opl.priv); + if (!(serial->pos_regs[2] & 1) && (val & 1)) + io_sethandler(0x0388, 0x0002, + serial->opl.read, NULL, NULL, + serial->opl.write, NULL, NULL, + serial->opl.priv); + break; + + default: + break; + } + + serial->pos_regs[port & 7] = val; +} + +uint8_t +opl2board_device_mca_feedb(void *priv) +{ + const opl2board_device_t *serial = (opl2board_device_t *) priv; + + return (serial->pos_regs[2] & 1); +} + +void * +opl2board_device_init(UNUSED(const device_t *info)) +{ + opl2board_device_t *serial = malloc(sizeof(opl2board_device_t)); + memset(serial, 0, sizeof(opl2board_device_t)); + + opl2board_device_log("opl2board_device_init\n"); + fm_driver_get(FM_OPL2BOARD, &serial->opl); + io_sethandler(0x0388, 0x0002, + serial->opl.read, NULL, NULL, + serial->opl.write, NULL, NULL, + serial->opl.priv); + music_add_handler(opl2board_device_get_buffer, serial); + + return serial; +} + +void * +opl2board_device_mca_init(const device_t *info) +{ + opl2board_device_t *serial = opl2board_device_init(info); + + io_removehandler(0x0388, 0x0002, + serial->opl.read, NULL, NULL, + serial->opl.write, NULL, NULL, + serial->opl.priv); + mca_add(opl2board_device_mca_read, + opl2board_device_mca_write, + opl2board_device_mca_feedb, + NULL, + serial); + serial->pos_regs[0] = 0xd7; + serial->pos_regs[1] = 0x70; + + return serial; +} + +void +opl2board_device_close(void *priv) +{ + opl2board_device_t *serial = (opl2board_device_t *) priv; + free(serial); +} + + +static const device_config_t opl2board_config[] = { +{ + .name = "host_serial_path", + .description = "Host Serial Device", + .type = CONFIG_SERPORT, + .default_string = "", + .file_filter = NULL, + .spinner = {}, + .selection = {} + }, + { .name = "", .description = "", .type = CONFIG_END } +}; + +const device_t opl2board_device = { + .name = "OPL2Board (External Device)", + .internal_name = "opl2board_device", + .flags = DEVICE_ISA, + .local = 0, + .init = opl2board_device_init, + .close = opl2board_device_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = opl2board_config +}; diff --git a/src/sound/snd_opl_opl2board.cpp b/src/sound/snd_opl_opl2board.cpp new file mode 100644 index 0000000000..91649f3724 --- /dev/null +++ b/src/sound/snd_opl_opl2board.cpp @@ -0,0 +1,550 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * Interface to the YMFM External audio device (USB) + * For OPL2Board arduino based. + * + * Authors: Jose Phillips, + * Adrien Moulin, + * + * Copyright 2024 Jose Phillips. + * Copyright 2022 Adrien Moulin. + */ +#include +#include +#include +#include +#include +#include "ymfm/ymfm_opl.h" +#include + + +extern "C" { +#define HAVE_STDARG_H +#include <86box/86box.h> +#include <86box/timer.h> +#include <86box/device.h> +#include <86box/sound.h> +#include <86box/snd_opl.h> +#include <86box/mem.h> +#include <86box/rom.h> +#include <86box/plat_unused.h> +#include <86box/config.h> +#include <86box/ini.h> +#include <86box/device.h> + + +// Disable c99-designator to avoid the warnings in *_ymfm_device +#ifdef __clang__ +# if __has_warning("-Wc99-designator") +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wc99-designator" +# endif +#endif + +} + + +#define RSM_FRAC 10 + +#define OPL_FREQ FREQ_48000 + +enum { + FLAG_CYCLES = (1 << 0) +}; + +uint8_t lastval = 0x00; + + +class OPLBOARDChipBase { +public: + OPLBOARDChipBase(UNUSED(uint32_t clock), fm_type type, uint32_t samplerate) + : m_buf_pos(0) + , m_flags(0) + , m_type(type) + , m_samplerate(samplerate) + { + memset(m_buffer, 0, sizeof(m_buffer)); + } + + virtual ~OPLBOARDChipBase() + { + } + + fm_type type() const { return m_type; } + int8_t flags() const { return m_flags; } + void set_do_cycles(int8_t do_cycles) { do_cycles ? m_flags |= FLAG_CYCLES : m_flags &= ~FLAG_CYCLES; } + int32_t *buffer() const { return (int32_t *) m_buffer; } + void reset_buffer() { m_buf_pos = 0; } + + virtual uint32_t sample_rate() const = 0; + + virtual void write(uint16_t addr, uint8_t data) = 0; + virtual void generate(int32_t *data, uint32_t num_samples) = 0; + virtual int32_t *update() = 0; + virtual uint8_t read(uint16_t addr) = 0; + virtual void set_clock(uint32_t clock) = 0; + + +protected: + int32_t m_buffer[MUSICBUFLEN * 2]; + int m_buf_pos; + int *m_buf_pos_global; + int8_t m_flags; + fm_type m_type; + uint32_t m_samplerate; +}; + +template +class OPLBOARDChip : public OPLBOARDChipBase, public ymfm::ymfm_interface { +public: + OPLBOARDChip(uint32_t clock, fm_type type, uint32_t samplerate) + : OPLBOARDChipBase(clock, type, samplerate) + , m_chip(*this) + , m_clock(clock) + , m_samplerate(samplerate) + , m_samplecnt(0) + { + memset(m_samples, 0, sizeof(m_samples)); + memset(m_oldsamples, 0, sizeof(m_oldsamples)); + m_rateratio = (samplerate << RSM_FRAC) / m_chip.sample_rate(m_clock); + m_clock_us = 1000000.0 / (double) m_clock; + m_subtract[0] = 80.0; + m_subtract[1] = 320.0; + m_type = type; + m_buf_pos_global = (samplerate == FREQ_49716) ? &music_pos_global : &wavetable_pos_global; + + if (m_type == FM_YMF278B) { + if (rom_load_linear("roms/sound/yamaha/yrw801.rom", 0, 0x200000, 0, m_yrw801) == 0) { + fatal("YRW801 ROM image \"roms/sound/yamaha/yrw801.rom\" not found\n"); + } + } + + timer_add(&m_timers[0], OPLBOARDChip::timer1, this, 0); + timer_add(&m_timers[1], OPLBOARDChip::timer2, this, 0); + } + + virtual uint32_t sample_rate() const override + { + return m_chip.sample_rate(m_clock); + } + + virtual void ymfm_set_timer(uint32_t tnum, int32_t duration_in_clocks) override + { + if (tnum > 1) + return; + + m_duration_in_clocks[tnum] = duration_in_clocks; + pc_timer_t *timer = &m_timers[tnum]; + if (duration_in_clocks < 0) + timer_stop(timer); + else { + double period = m_clock_us * duration_in_clocks; + if (period < m_subtract[tnum]) + m_engine->engine_timer_expired(tnum); + else + timer_on_auto(timer, period); + } + } + + virtual void set_clock(uint32_t clock) override + { + m_clock = clock; + m_clock_us = 1000000.0 / (double) m_clock; + m_rateratio = (m_samplerate << RSM_FRAC) / m_chip.sample_rate(m_clock); + + ymfm_set_timer(0, m_duration_in_clocks[0]); + ymfm_set_timer(1, m_duration_in_clocks[1]); + } + + virtual void generate(int32_t *data, uint32_t num_samples) override + { + for (uint32_t i = 0; i < num_samples; i++) { + m_chip.generate(&m_output); + if ((m_type == FM_YMF278B) && (sizeof(m_output.data) > (4 * sizeof(int32_t)))) { + if (ChipType::OUTPUTS == 1) { + *data++ = m_output.data[4]; + *data++ = m_output.data[4]; + } else { + *data++ = m_output.data[4]; + *data++ = m_output.data[5]; + } + } else if (ChipType::OUTPUTS == 1) { + *data++ = m_output.data[0]; + *data++ = m_output.data[0]; + } else { + *data++ = m_output.data[0]; + *data++ = m_output.data[1 % ChipType::OUTPUTS]; + } + } + } + +#if 0 + virtual void generate_resampled(int32_t *data, uint32_t num_samples) override + { + if ((m_samplerate == FREQ_49716) || (m_samplerate == FREQ_44100)) { + generate(data, num_samples); + return; + } + + for (uint32_t i = 0; i < num_samples; i++) { + while (m_samplecnt >= m_rateratio) { + m_oldsamples[0] = m_samples[0]; + m_oldsamples[1] = m_samples[1]; + m_chip.generate(&m_output); + if ((m_type == FM_YMF278B) && (sizeof(m_output.data) > (4 * sizeof(int32_t)))) { + if (ChipType::OUTPUTS == 1) { + m_samples[0] = m_output.data[4]; + m_samples[1] = m_output.data[4]; + } else { + m_samples[0] = m_output.data[4]; + m_samples[1] = m_output.data[5]; + } + } else if (ChipType::OUTPUTS == 1) { + m_samples[0] = m_output.data[0]; + m_samples[1] = m_output.data[0]; + } else { + m_samples[0] = m_output.data[0]; + m_samples[1] = m_output.data[1 % ChipType::OUTPUTS]; + } + m_samplecnt -= m_rateratio; + } + + *data++ = ((int32_t) ((m_oldsamples[0] * (m_rateratio - m_samplecnt) + + m_samples[0] * m_samplecnt) + / m_rateratio)); + *data++ = ((int32_t) ((m_oldsamples[1] * (m_rateratio - m_samplecnt) + + m_samples[1] * m_samplecnt) + / m_rateratio)); + + m_samplecnt += 1 << RSM_FRAC; + } + } +#endif + + virtual int32_t *update() override + { + if (m_buf_pos >= *m_buf_pos_global) + return m_buffer; + + generate(&m_buffer[m_buf_pos * 2], *m_buf_pos_global - m_buf_pos); + + for (; m_buf_pos < *m_buf_pos_global; m_buf_pos++) { + m_buffer[m_buf_pos * 2] /= 2; + m_buffer[(m_buf_pos * 2) + 1] /= 2; + } + + return m_buffer; + } + + virtual void write(uint16_t addr, uint8_t data) override + { + + m_chip.write(addr, data); + } + + virtual uint8_t read(uint16_t addr) override + { + return m_chip.read(addr); + } + + virtual uint32_t get_special_flags(void) override + { + return ((m_type == FM_YMF262) || (m_type == FM_YMF289B) || (m_type == FM_YMF278B)) ? 0x8000 : 0x0000; + } + + static void timer1(void *priv) + { + OPLBOARDChip *drv = (OPLBOARDChip *) priv; + drv->m_engine->engine_timer_expired(0); + } + + static void timer2(void *priv) + { + OPLBOARDChip *drv = (OPLBOARDChip *) priv; + drv->m_engine->engine_timer_expired(1); + } + + virtual uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address) override + { + if (type == ymfm::access_class::ACCESS_PCM && address < 0x200000) { + return m_yrw801[address]; + } + return 0xFF; + } + +private: + ChipType m_chip; + uint32_t m_clock; + double m_clock_us; + double m_subtract[2]; + typename ChipType::output_data m_output; + pc_timer_t m_timers[2]; + int32_t m_duration_in_clocks[2]; // Needed for clock switches. + uint32_t m_samplerate; + + // YRW801-M wavetable ROM. + uint8_t m_yrw801[0x200000]; + + // Resampling + int32_t m_rateratio; + int32_t m_samplecnt; + int32_t m_oldsamples[2]; + int32_t m_samples[2]; +}; + +extern "C" { +#include +#include +#include +#include +#include +#include +#define HAVE_STDARG_H + +#include "cpu.h" +#include <86box/86box.h> +#include <86box/io.h> +#include <86box/snd_opl.h> +#include <86box/device.h> +#include <86box/config.h> +#include <86box/ini.h> + + +#ifdef ENABLE_OPL_LOG +int ymfm_do_log = ENABLE_OPL_LOG; + +static void +ymfm_log(const char *fmt, ...) +{ + va_list ap; + + if (ymfm_do_log) { + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); + } +} +#else +# define ymfm_log(fmt, ...) +#endif + +struct sp_port *port; + + + +void opl2board_init() { + + device_add(&opl2board_device); + const char* port_name = device_get_config_string("host_serial_path"); + device_context_restore(); + + enum sp_return result; + + result = sp_get_port_by_name(port_name, &port); + if (result != SP_OK) { + ymfm_log("Error: Cannot find port %s\n", port_name); + return; + } + + result = sp_open(port, SP_MODE_READ_WRITE); + if (result != SP_OK) { + ymfm_log ("Error: Cannot open port %s\n", port_name); + return; + } + + // Set port configuration this values are hardcoded. + sp_set_baudrate(port, 115200); + sp_set_bits(port, 8); + sp_set_parity(port, SP_PARITY_NONE); + sp_set_stopbits(port, 1); + sp_set_flowcontrol(port, SP_FLOWCONTROL_NONE); + + + ymfm_log("OPL2Board Serial port %s initialized at 115200 baud.\n", port_name); + +} + + +void opl2board_write(uint8_t data) { + if (port == NULL) { + ymfm_log(stderr, "Error: OPL2Board Port not initialized.\n"); + + return; + } + + enum sp_return result = sp_blocking_write(port, &data, sizeof(data), 1000); + if (result < 0) { + ymfm_log(stderr, "Error: Failed to write to OPL2Board port.\n"); + } else { + ymfm_log("OPL2Board: data sent: %02X\n", data); + } +} + + +void opl2board_reset() { + + // Reset all voices to 0 + ymfm_log("Performing OPL2Board reset\n"); + for (uint8_t i = 0x00; i < 0xFF; i++) { + if (i >= 0x40 && i <= 0x55) { + opl2board_write(i); + opl2board_write(0x3F); + } else { + opl2board_write (i); + opl2board_write(0x00); + } } +} + +void opl2board_close() { + + if (port != NULL) { + opl2board_reset(); + sp_close(port); + sp_free_port(port); + port = NULL; + ymfm_log("OPL2Board port closed.\n"); + } +} + + +static void * +ymfm_opl2board_drv_init(const device_t *info) +{ + OPLBOARDChipBase *fm; + + switch (info->local) { + default: + case FM_OPL2BOARD: + fm = (OPLBOARDChipBase *) new OPLBOARDChip(3579545, FM_OPL2BOARD, FREQ_49716); + break; + } + fm->set_do_cycles(1); + + return fm; +} + +static void +ymfm_opl2board_drv_close(void *priv) +{ + OPLBOARDChipBase *drv = (OPLBOARDChipBase *) priv; + opl2board_close(); + if (drv != NULL) + delete drv; +} + +static uint8_t +ymfm_opl2board_drv_read(uint16_t port, void *priv) +{ + OPLBOARDChipBase *drv = (OPLBOARDChipBase *) priv; + + if ((port == 0x380) || (port == 0x381)) + port |= 4; + + /* Point to register read port. */ + if (drv->flags() & FLAG_CYCLES) + cycles -= ((int) (isa_timing * 8)); + + uint8_t ret = drv->read(port); + drv->update(); + + ymfm_log("YMFM read port %04x, status = %02x\n", port, ret); + return ret; +} + +static void +ymfm_opl2board_drv_write(uint16_t port, uint8_t val, void *priv) +{ + + OPLBOARDChipBase *drv = (OPLBOARDChipBase *) priv; + + ymfm_log("YMFM write port %04x value = %02x\n", port, val); + if ((port == 0x380) || (port == 0x381)) + port |= 4; + // Allow initialization of adlib + if ((val == 0x04 || val == 0x02) || (lastval == 0x04 || lastval == 0x02)) { + drv->write(port, val); + } + lastval = val; + opl2board_write(val); + drv->update(); +} + + +static int32_t * +ymfm_opl2board_drv_update(void *priv) +{ + if (port == NULL) { + opl2board_init(); + opl2board_reset(); + } + OPLBOARDChipBase *drv = (OPLBOARDChipBase *) priv; + + return drv->update(); +} + +static void +ymfm_opl2board_drv_reset_buffer(void *priv) +{ + OPLBOARDChipBase *drv = (OPLBOARDChipBase *) priv; + + drv->reset_buffer(); +} + +static void +ymfm_opl2board_drv_set_do_cycles(void *priv, int8_t do_cycles) +{ + OPLBOARDChipBase *drv = (OPLBOARDChipBase *) priv; + drv->set_do_cycles(do_cycles); +} + +static void +ymfm_opl2board_drv_generate(void *priv, int32_t *data, uint32_t num_samples) +{ + + OPLBOARDChipBase *drv = (OPLBOARDChipBase *) priv; + // drv->generate_resampled(data, num_samples); + drv->generate(data, num_samples); + +} + + + + +const device_t ym_opl2board_device = { + .name = "YMOPL2Board (External Device)", + .internal_name = "ym_opl2board_device", + .flags = 0, + .local = FM_OPL2BOARD, + .init = ymfm_opl2board_drv_init, + .close = ymfm_opl2board_drv_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + +const fm_drv_t ymfm_opl2board_drv { + &ymfm_opl2board_drv_read, + &ymfm_opl2board_drv_write, + &ymfm_opl2board_drv_update, + &ymfm_opl2board_drv_reset_buffer, + &ymfm_opl2board_drv_set_do_cycles, + NULL, + ymfm_opl2board_drv_generate + +}; + +#ifdef __clang__ +# if __has_warning("-Wc99-designator") +# pragma clang diagnostic pop +# endif +#endif + +} diff --git a/src/sound/snd_resid.cpp b/src/sound/snd_resid.cpp index d7082e47e9..b9895cf7ee 100644 --- a/src/sound/snd_resid.cpp +++ b/src/sound/snd_resid.cpp @@ -20,22 +20,21 @@ typedef struct psid_t { psid_t *psid; void * -sid_init(void) +sid_init(uint8_t type) { -#if 0 - psid_t *psid; -#endif - reSIDfp::SamplingMethod method = reSIDfp::DECIMATE; + reSIDfp::SamplingMethod method = reSIDfp::RESAMPLE; float cycles_per_sec = 14318180.0 / 16.0; - psid = new psid_t; -#if 0 - psid = (psid_t *) malloc(sizeof(sound_t)); -#endif + psid = new psid_t; psid->sid = new SID; - psid->sid->setChipModel(reSIDfp::MOS8580); - psid->sid->enableFilter(true); + switch (type) { + default: + case 0: + psid->sid->setChipModel(reSIDfp::MOS6581); + case 1: + psid->sid->setChipModel(reSIDfp::MOS8580); + } psid->sid->reset(); @@ -43,14 +42,13 @@ sid_init(void) psid->sid->write(c, 0); try { - psid->sid->setSamplingParameters(cycles_per_sec, method, (float) RESID_FREQ, 0.9 * (float) RESID_FREQ / 2.0); + psid->sid->setSamplingParameters(cycles_per_sec, method, (float) RESID_FREQ); } catch (reSIDfp::SIDError) { #if 0 printf("reSID failed!\n"); #endif } - psid->sid->setChipModel(reSIDfp::MOS6581); psid->sid->input(0); return (void *) psid; @@ -59,9 +57,6 @@ sid_init(void) void sid_close(UNUSED(void *priv)) { -#if 0 - psid_t *psid = (psid_t *) priv; -#endif delete psid->sid; #if 0 free(psid); @@ -71,10 +66,6 @@ sid_close(UNUSED(void *priv)) void sid_reset(UNUSED(void *priv)) { -#if 0 - psid_t *psid = (psid_t *) priv; -#endif - psid->sid->reset(); for (uint8_t c = 0; c < 32; c++) @@ -84,23 +75,12 @@ sid_reset(UNUSED(void *priv)) uint8_t sid_read(uint16_t addr, UNUSED(void *priv)) { -#if 0 - psid_t *psid = (psid_t *) priv; -#endif - return psid->sid->read(addr & 0x1f); -#if 0 - return 0xFF; -#endif } void sid_write(uint16_t addr, uint8_t val, UNUSED(void *priv)) { -#if 0 - psid_t *psid = (psid_t *) priv; -#endif - psid->sid->write(addr & 0x1f, val); } @@ -118,9 +98,6 @@ fillbuf2(int &count, int16_t *buf, int len) void sid_fillbuf(int16_t *buf, int len, UNUSED(void *priv)) { -#if 0 - psid_t *psid = (psid_t *) priv; -#endif int x = CLOCK_DELTA(len); fillbuf2(x, buf, len); diff --git a/src/sound/snd_sb.c b/src/sound/snd_sb.c index 6536071b04..0a01a4be56 100644 --- a/src/sound/snd_sb.c +++ b/src/sound/snd_sb.c @@ -1452,9 +1452,6 @@ ess_mixer_write(uint16_t addr, uint8_t val, void *priv) if (!(addr & 1)) { mixer->index = val; mixer->regs[0x01] = val; - if (val == 0x40) { - mixer->ess_id_str_pos = 0; - } } else { if (mixer->index == 0) { /* Reset */ @@ -1473,6 +1470,8 @@ ess_mixer_write(uint16_t addr, uint8_t val, void *priv) mixer->regs[0x3c] = 0x05; mixer->regs[0x3e] = 0x00; + mixer->regs[0x64] = 0x08; + sb_dsp_set_stereo(&ess->dsp, mixer->regs[0x0e] & 2); } else { mixer->regs[mixer->index] = val; @@ -1525,6 +1524,7 @@ ess_mixer_write(uint16_t addr, uint8_t val, void *priv) break; case 0x1C: + mixer->regs[mixer->index] = val & 0x2f; if ((mixer->regs[0x1C] & 0x07) == 0x07) { mixer->input_selector = INPUT_MIXER_L | INPUT_MIXER_R; } else if ((mixer->regs[0x1C] & 0x07) == 0x06) { @@ -1556,12 +1556,12 @@ ess_mixer_write(uint16_t addr, uint8_t val, void *priv) break; case 0x64: - mixer->regs[mixer->index] = (mixer->regs[mixer->index] & 0xf7) | 0x20; - // mixer->regs[mixer->index] &= ~0x8; + if (ess->dsp.sb_subtype > SB_SUBTYPE_ESS_ES1688) + mixer->regs[mixer->index] = (mixer->regs[mixer->index] & 0xf7) | 0x20; break; case 0x40: - { + if (ess->dsp.sb_subtype >= SB_SUBTYPE_ESS_ES1688) { uint16_t mpu401_base_addr = 0x300 | ((mixer->regs[0x40] << 1) & 0x30); sb_log("mpu401_base_addr = %04X\n", mpu401_base_addr); @@ -1572,7 +1572,7 @@ ess_mixer_write(uint16_t addr, uint8_t val, void *priv) gameport_remap(ess->gameport, !(mixer->regs[0x40] & 0x2) ? 0x00 : 0x200); - if (ess->dsp.sb_subtype != SB_SUBTYPE_ESS_ES1688) { + if (ess->dsp.sb_subtype > SB_SUBTYPE_ESS_ES1688) { /* Not on ES1688. */ io_removehandler(0x0388, 0x0004, ess->opl.read, NULL, NULL, @@ -1585,6 +1585,7 @@ ess_mixer_write(uint16_t addr, uint8_t val, void *priv) ess->opl.priv); } } + if (ess->mpu != NULL) switch ((mixer->regs[0x40] >> 5) & 0x7) { default: break; @@ -1627,11 +1628,12 @@ ess_mixer_write(uint16_t addr, uint8_t val, void *priv) ess_fm_midi_read, NULL, NULL, ess_fm_midi_write, NULL, NULL, ess); - break; } + break; default: - sb_log("ess: Unknown mixer register WRITE: %02X\t%02X\n", mixer->index, mixer->regs[mixer->index]); + sb_log("ess: Unknown mixer register WRITE: %02X\t%02X\n", + mixer->index, mixer->regs[mixer->index]); break; } } @@ -1667,6 +1669,7 @@ ess_mixer_read(uint16_t addr, void *priv) case 0x0c: case 0x0e: case 0x14: + case 0x1a: case 0x02: case 0x06: case 0x30: @@ -1685,20 +1688,48 @@ ess_mixer_read(uint16_t addr, void *priv) ret = mixer->regs[mixer->index] | 0x11; break; + /* Bit 1 always set, bits 7-6 always clear on both the real ES688 and ES1688. */ + case 0x1c: + ret = mixer->regs[mixer->index] | 0x10; + break; + + /* + Real ES688: Always 0x00; + Real ES1688: Bit 2 always clear. + */ case 0x40: - if (ess->dsp.sb_subtype == SB_SUBTYPE_ESS_ES1688) + if (ess->dsp.sb_subtype > SB_SUBTYPE_ESS_ES1688) ret = mixer->regs[mixer->index]; + else if (ess->dsp.sb_subtype >= SB_SUBTYPE_ESS_ES1688) + ret = mixer->regs[mixer->index] & 0xfb; else - ret = 0x0a; + ret = 0x00; break; + /* + Real ES688: Always 0x00; + Real ES1688: All bits writable. + */ case 0x48: - ret = mixer->regs[mixer->index]; - break; + if (ess->dsp.sb_subtype >= SB_SUBTYPE_ESS_ES1688) + ret = mixer->regs[mixer->index]; + else + ret = 0x00; + break; - /* Return 0x00 so it has bit 3 clear, so NT 5.x drivers don't misdetect it as ES1788. */ + /* + Return 0x00 so it has bit 3 clear, so NT 5.x drivers don't misdetect it as ES1788. + Bit 3 set and writable: ESSCFG detects the card as ES1788 if register 70h is read-only, + otherwise, as ES1887. + Bit 3 set and read-only: ESSCFG detects the card as ES1788 if register 70h is read-only, + otherwise, as ES1888. + Real ES688 and ES1688: Always 0x00. + */ case 0x64: - ret = (mixer->regs[mixer->index] & 0xf7) | 0x20; + if (ess->dsp.sb_subtype > SB_SUBTYPE_ESS_ES1688) + ret = (mixer->regs[mixer->index] & 0xf7) | 0x20; + else + ret = 0x00; break; default: @@ -1706,7 +1737,7 @@ ess_mixer_read(uint16_t addr, void *priv) break; } - sb_log("[%04X:%08X] [R] %04X = %02X\n", CS, cpu_state.pc, addr, ret); + sb_log("[%04X:%08X] [R] %04X = %02X (%02X)\n", CS, cpu_state.pc, addr, ret, mixer->index); return ret; } @@ -2268,9 +2299,6 @@ ess_x688_pnp_config_changed(UNUSED(const uint8_t ld), isapnp_device_config_t *co ess_base_write, NULL, NULL, ess); - ess->mixer_ess.ess_id_str[2] = 0x00; - ess->mixer_ess.ess_id_str[3] = 0x00; - addr = ess->opl_pnp_addr; if (addr) { ess->opl_pnp_addr = 0; @@ -2337,9 +2365,6 @@ ess_x688_pnp_config_changed(UNUSED(const uint8_t ld), isapnp_device_config_t *co ess_base_read, NULL, NULL, ess_base_write, NULL, NULL, ess); - - ess->mixer_ess.ess_id_str[2] = (addr >> 8) & 0xff; - ess->mixer_ess.ess_id_str[3] = addr & 0xff; } addr = config->io[1].base; @@ -3748,11 +3773,6 @@ ess_x688_init(UNUSED(const device_t *info)) midi_in_handler(1, sb_dsp_input_msg, sb_dsp_input_sysex, &ess->dsp); if (info->local) { - ess->mixer_ess.ess_id_str[0] = 0x16; - ess->mixer_ess.ess_id_str[1] = 0x88; - ess->mixer_ess.ess_id_str[2] = (addr >> 8) & 0xff; - ess->mixer_ess.ess_id_str[3] = addr & 0xff; - ess->mpu = (mpu_t *) calloc(1, sizeof(mpu_t)); /* NOTE: The MPU is initialized disabled and with no IRQ assigned. * It will be later initialized by the guest OS's drivers. */ @@ -3818,12 +3838,6 @@ ess_x688_pnp_init(UNUSED(const device_t *info)) if (device_get_config_int("receive_input")) midi_in_handler(1, sb_dsp_input_msg, sb_dsp_input_sysex, &ess->dsp); - /* Not on ES688. */ - ess->mixer_ess.ess_id_str[0] = 0x16; - ess->mixer_ess.ess_id_str[1] = 0x88; - ess->mixer_ess.ess_id_str[2] = 0x00; - ess->mixer_ess.ess_id_str[3] = 0x00; - ess->mpu = (mpu_t *) calloc(1, sizeof(mpu_t)); /* NOTE: The MPU is initialized disabled and with no IRQ assigned. * It will be later initialized by the guest OS's drivers. */ diff --git a/src/sound/snd_sb_dsp.c b/src/sound/snd_sb_dsp.c index 4cfd2c7bbe..0063f0ae7b 100644 --- a/src/sound/snd_sb_dsp.c +++ b/src/sound/snd_sb_dsp.c @@ -1687,6 +1687,10 @@ sb_exec_command(sb_dsp_t *dsp) break; case 0xE1: /* Get DSP version */ if (IS_ESS(dsp)) { + /* + 0x03 0x01 (Sound Blaster Pro compatibility) confirmed by both the + ES1888 datasheet and the probing of the real ES688 and ES1688 cards. + */ sb_add_data(dsp, 0x3); sb_add_data(dsp, 0x1); break; @@ -1722,9 +1726,12 @@ sb_exec_command(sb_dsp_t *dsp) while (sb16_copyright[c]) sb_add_data(dsp, sb16_copyright[c++]); sb_add_data(dsp, 0); - } else if (IS_ESS(dsp)) { - sb_add_data(dsp, 0); - } + } /* else if (IS_ESS(dsp)) + sb_add_data(dsp, 0); */ + /* + TODO: What ESS card returns 0x00 here? Probing of the real ES688 and ES1688 cards + revealed that they in fact return nothing on this command. + */ break; case 0xE4: /* Write test register */ dsp->sb_test = dsp->sb_data[0]; @@ -1736,12 +1743,26 @@ sb_exec_command(sb_dsp_t *dsp) break; case SB_SUBTYPE_ESS_ES688: sb_add_data(dsp, 0x68); - sb_add_data(dsp, 0x80 | 0x04); + /* + 80h: ESSCFG fails to detect the AudioDrive; + 81h-83h: ES??88, Windows 3.1 driver expects MPU-401 and gives a legacy mixer error; + 84h: ES688, Windows 3.1 driver expects MPU-401, returned by DOSBox-X; + 85h-87h: ES688, Windows 3.1 driver does not expect MPU-401: + 85h: Returned by MSDOS622's real ESS688, + 86h: Returned by Dizzy's real ES688. + We return 86h if MPU is absent, 84h otherwise, who knows what the actual + PnP ES688 returns here. + */ + sb_add_data(dsp, 0x80 | ((dsp->mpu != NULL) ? 0x04 : 0x06)); break; case SB_SUBTYPE_ESS_ES1688: - // Determined via Windows driver debugging. sb_add_data(dsp, 0x68); - sb_add_data(dsp, 0x80 | 0x09); + /* + 89h: ES1688, returned by DOSBox-X, determined via Windows driver + debugging; + 8Bh: ES1688, returned by both MSDOS622's and Dizzy's real ES1688's. + */ + sb_add_data(dsp, 0x80 | 0x0b); break; } } diff --git a/src/sound/snd_ssi2001.c b/src/sound/snd_ssi2001.c index 1f3c294ced..11a10473c1 100644 --- a/src/sound/snd_ssi2001.c +++ b/src/sound/snd_ssi2001.c @@ -21,6 +21,10 @@ typedef struct ssi2001_t { int gameport_enabled; } ssi2001_t; +typedef struct entertainer_t { + uint8_t regs; +} entertainer_t; + static void ssi2001_update(ssi2001_t *ssi2001) { @@ -69,7 +73,7 @@ ssi2001_init(UNUSED(const device_t *info)) ssi2001_t *ssi2001 = malloc(sizeof(ssi2001_t)); memset(ssi2001, 0, sizeof(ssi2001_t)); - ssi2001->psid = sid_init(); + ssi2001->psid = sid_init(0); sid_reset(ssi2001->psid); uint16_t addr = device_get_config_hex16("base"); ssi2001->gameport_enabled = device_get_config_int("gameport"); @@ -90,6 +94,48 @@ ssi2001_close(void *priv) free(ssi2001); } +static uint8_t +entertainer_read(uint16_t addr, void *priv) +{ + return 0xa5; +} + +static void +entertainer_write(uint16_t addr, uint8_t val, void *priv) +{ + entertainer_t *entertainer = (entertainer_t *) priv; + entertainer->regs = val; +} + +void * +entertainer_init(UNUSED(const device_t *info)) +{ + ssi2001_t *ssi2001 = malloc(sizeof(ssi2001_t)); + entertainer_t *entertainer = malloc(sizeof(entertainer_t)); + memset(ssi2001, 0, sizeof(ssi2001_t)); + memset(entertainer, 0, sizeof(entertainer_t)); + + ssi2001->psid = sid_init(0); + sid_reset(ssi2001->psid); + ssi2001->gameport_enabled = device_get_config_int("gameport"); + io_sethandler(0x200, 0x0001, entertainer_read, NULL, NULL, entertainer_write, NULL, NULL, entertainer); + io_sethandler(0x280, 0x0020, ssi2001_read, NULL, NULL, ssi2001_write, NULL, NULL, ssi2001); + if (ssi2001->gameport_enabled) + gameport_remap(gameport_add(&gameport_201_device), 0x201); + sound_add_handler(ssi2001_get_buffer, ssi2001); + return ssi2001; +} + +void +entertainer_close(void *priv) +{ + ssi2001_t *ssi2001 = (ssi2001_t *) priv; + + sid_close(ssi2001->psid); + + free(ssi2001); +} + static const device_config_t ssi2001_config[] = { // clang-format off { @@ -125,17 +171,37 @@ static const device_config_t ssi2001_config[] = { // clang-format off }; -const device_t ssi2001_device = -{ - .name = "Innovation SSI-2001", +static const device_config_t entertainer_config[] = { + // clang-format off + { "gameport", "Enable Game port", CONFIG_BINARY, "", 1 }, + { "", "", -1 } +// clang-format off +}; + +const device_t ssi2001_device = { + .name = "Innovation SSI-2001", .internal_name = "ssi2001", - .flags = DEVICE_ISA, - .local = 0, - .init = ssi2001_init, - .close = ssi2001_close, - .reset = NULL, - { .available = NULL }, + .flags = DEVICE_ISA, + .local = 0, + .init = ssi2001_init, + .close = ssi2001_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = ssi2001_config +}; + +const device_t entertainer_device = { + .name = "The Entertainer", + .internal_name = "Entertainer", + .flags = DEVICE_ISA, + .local = 1, + .init = entertainer_init, + .close = entertainer_close, + .reset = NULL, + { .available = NULL }, .speed_changed = NULL, - .force_redraw = NULL, - .config = ssi2001_config + .force_redraw = NULL, + .config = entertainer_config }; diff --git a/src/sound/sound.c b/src/sound/sound.c index 254e529ca4..d2d3dc3133 100644 --- a/src/sound/sound.c +++ b/src/sound/sound.c @@ -132,6 +132,7 @@ static const SOUND_CARD sound_cards[] = { { &sb_vibra16s_device }, { &sb_vibra16xv_device }, { &ssi2001_device }, + { &entertainer_device }, { &pasplus_device }, { &pas16_device }, { &pas16d_device }, @@ -153,6 +154,9 @@ static const SOUND_CARD sound_cards[] = { { &ct5880_device }, { &ad1881_device }, { &cs4297a_device }, +#ifdef USE_LIBSERIALPORT /*The following devices required LIBSERIALPORT*/ + { &opl2board_device }, +#endif { NULL } // clang-format on }; diff --git a/src/sound/ymfm/ymfm.h b/src/sound/ymfm/ymfm.h index c5986d66b7..062247a820 100644 --- a/src/sound/ymfm/ymfm.h +++ b/src/sound/ymfm/ymfm.h @@ -42,12 +42,11 @@ #include #include #include +#include #include #include #include -#define SNPRINTF_BUFFER_SIZE_CALC (256 - (end - &buffer[0])) - namespace ymfm { @@ -111,17 +110,6 @@ inline int32_t clamp(int32_t value, int32_t minval, int32_t maxval) } -//------------------------------------------------- -// array_size - return the size of an array -//------------------------------------------------- - -template -constexpr uint32_t array_size(ArrayType (&array)[ArraySize]) -{ - return ArraySize; -} - - //------------------------------------------------- // count_leading_zeros - return the number of // leading zeros in a 32-bit value; CPU-optimized @@ -256,7 +244,8 @@ inline int16_t roundtrip_fp(int32_t value) // apply the shift back and forth to zero out bits that are lost exponent -= 1; - return (value >> exponent) << exponent; + int32_t mask = (1 << exponent) - 1; + return value & ~mask; } @@ -352,7 +341,7 @@ class ymfm_wavfile { // create file char name[20]; - snprintf(name, sizeof(name), "wavlog-%02d.wav", m_index); + snprintf(&name[0], sizeof(name), "wavlog-%02d.wav", m_index); FILE *out = fopen(name, "wb"); // make the wav file header @@ -485,7 +474,7 @@ class ymfm_saved_state class ymfm_engine_callbacks { public: - virtual ~ymfm_engine_callbacks() = default; + virtual ~ymfm_engine_callbacks() = default; // timer callback; called by the interface when a timer fires virtual void engine_timer_expired(uint32_t tnum) = 0; @@ -509,6 +498,7 @@ class ymfm_interface public: virtual ~ymfm_interface() = default; + // the following functions must be implemented by any derived classes; the // default implementations are sufficient for some minimal operation, but will // likely need to be overridden to integrate with the outside world; they are diff --git a/src/sound/ymfm/ymfm_fm.h b/src/sound/ymfm/ymfm_fm.h index 81795f8fe4..d40409fddf 100644 --- a/src/sound/ymfm/ymfm_fm.h +++ b/src/sound/ymfm/ymfm_fm.h @@ -267,7 +267,7 @@ class fm_channel // assign operators void assign(uint32_t index, fm_operator *op) { - assert(index < array_size(m_op)); + assert(index < m_op.size()); m_op[index] = op; if (op != nullptr) op->set_choffs(m_choffs); @@ -330,7 +330,7 @@ class fm_channel uint32_t m_choffs; // channel offset in registers int16_t m_feedback[2]; // feedback memory for operator 1 mutable int16_t m_feedback_in; // next input value for op 1 feedback (set in output) - fm_operator *m_op[4]; // up to 4 operators + std::array *, 4> m_op; // up to 4 operators RegisterType &m_regs; // direct reference to registers fm_engine_base &m_owner; // reference to the owning engine }; diff --git a/src/sound/ymfm/ymfm_fm.ipp b/src/sound/ymfm/ymfm_fm.ipp index a3ee8d3332..2aa0a216bd 100644 --- a/src/sound/ymfm/ymfm_fm.ipp +++ b/src/sound/ymfm/ymfm_fm.ipp @@ -839,12 +839,12 @@ void fm_channel::save_restore(ymfm_saved_state &state) template void fm_channel::keyonoff(uint32_t states, keyon_type type, uint32_t chnum) { - for (uint32_t opnum = 0; opnum < array_size(m_op); opnum++) + for (uint32_t opnum = 0; opnum < m_op.size(); opnum++) if (m_op[opnum] != nullptr) m_op[opnum]->keyonoff(bitfield(states, opnum), type); if (debug::LOG_KEYON_EVENTS && ((debug::GLOBAL_FM_CHANNEL_MASK >> chnum) & 1) != 0) - for (uint32_t opnum = 0; opnum < array_size(m_op); opnum++) + for (uint32_t opnum = 0; opnum < m_op.size(); opnum++) if (m_op[opnum] != nullptr) debug::log_keyon("%c%s\n", bitfield(states, opnum) ? '+' : '-', m_regs.log_keyon(m_choffs, m_op[opnum]->opoffs()).c_str()); } @@ -860,7 +860,7 @@ bool fm_channel::prepare() uint32_t active_mask = 0; // prepare all operators and determine if they are active - for (uint32_t opnum = 0; opnum < array_size(m_op); opnum++) + for (uint32_t opnum = 0; opnum < m_op.size(); opnum++) if (m_op[opnum] != nullptr) if (m_op[opnum]->prepare()) active_mask |= 1 << opnum; @@ -880,7 +880,7 @@ void fm_channel::clock(uint32_t env_counter, int32_t lfo_raw_pm) m_feedback[0] = m_feedback[1]; m_feedback[1] = m_feedback_in; - for (uint32_t opnum = 0; opnum < array_size(m_op); opnum++) + for (uint32_t opnum = 0; opnum < m_op.size(); opnum++) if (m_op[opnum] != nullptr) m_op[opnum]->clock(env_counter, lfo_raw_pm); @@ -888,7 +888,7 @@ void fm_channel::clock(uint32_t env_counter, int32_t lfo_raw_pm) useful temporary code for envelope debugging if (m_choffs == 0x101) { - for (uint32_t opnum = 0; opnum < array_size(m_op); opnum++) + for (uint32_t opnum = 0; opnum < m_op.size(); opnum++) { auto &op = *m_op[((opnum & 1) << 1) | ((opnum >> 1) & 1)]; printf(" %c%03X%c%c ", @@ -1473,14 +1473,11 @@ void fm_engine_base::assign_operators() template void fm_engine_base::update_timer(uint32_t tnum, uint32_t enable, int32_t delta_clocks) { - uint32_t subtract = !!(tnum >> 15); - tnum &= 0x7fff; - // if the timer is live, but not currently enabled, set the timer if (enable && !m_timer_running[tnum]) { // period comes from the registers, and is different for each - uint32_t period = (tnum == 0) ? (1024 - subtract - m_regs.timer_a_value()) : 16 * (256 - subtract - m_regs.timer_b_value()); + uint32_t period = (tnum == 0) ? (1024 - m_regs.timer_a_value()) : 16 * (256 - m_regs.timer_b_value()); // caller can also specify a delta to account for other effects period += delta_clocks; @@ -1507,6 +1504,8 @@ void fm_engine_base::update_timer(uint32_t tnum, uint32_t enable, template void fm_engine_base::engine_timer_expired(uint32_t tnum) { + assert(tnum == 0 || tnum == 1); + // update status if (tnum == 0 && m_regs.enable_timer_a()) set_reset_status(STATUS_TIMERA, 0); @@ -1522,11 +1521,8 @@ void fm_engine_base::engine_timer_expired(uint32_t tnum) m_modified_channels |= 1 << chnum; } - // Make sure the array does not go out of bounds to keep gcc happy - if ((tnum < 2) || (sizeof(m_timer_running) > (2 * sizeof(uint8_t)))) { - // reset - m_timer_running[tnum] = false; - } + // reset + m_timer_running[tnum] = false; update_timer(tnum, 1, 0); } diff --git a/src/sound/ymfm/ymfm_opl.cpp b/src/sound/ymfm/ymfm_opl.cpp index bb91c5dc0b..8e8025fd9c 100644 --- a/src/sound/ymfm/ymfm_opl.cpp +++ b/src/sound/ymfm/ymfm_opl.cpp @@ -386,9 +386,9 @@ std::string opl_registers_base::log_keyon(uint32_t choffs, uint32_t op uint32_t opnum = (opoffs & 31) - 2 * ((opoffs & 31) / 8) + 18 * bitfield(opoffs, 8); char buffer[256]; - char *end = &buffer[0]; + int end = 0; - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, "%2u.%02u freq=%04X fb=%u alg=%X mul=%X tl=%02X ksr=%u ns=%u ksl=%u adr=%X/%X/%X sl=%X sus=%u", + end += snprintf(&buffer[end], sizeof(buffer) - end, "%2u.%02u freq=%04X fb=%u alg=%X mul=%X tl=%02X ksr=%u ns=%u ksl=%u adr=%X/%X/%X sl=%X sus=%u", chnum, opnum, ch_block_freq(choffs), ch_feedback(choffs), @@ -405,25 +405,25 @@ std::string opl_registers_base::log_keyon(uint32_t choffs, uint32_t op op_eg_sustain(opoffs)); if (OUTPUTS > 1) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " out=%c%c%c%c", + end += snprintf(&buffer[end], sizeof(buffer) - end, " out=%c%c%c%c", ch_output_0(choffs) ? 'L' : '-', ch_output_1(choffs) ? 'R' : '-', ch_output_2(choffs) ? '0' : '-', ch_output_3(choffs) ? '1' : '-'); if (op_lfo_am_enable(opoffs) != 0) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " am=%u", lfo_am_depth()); + end += snprintf(&buffer[end], sizeof(buffer) - end, " am=%u", lfo_am_depth()); if (op_lfo_pm_enable(opoffs) != 0) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " pm=%u", lfo_pm_depth()); + end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=%u", lfo_pm_depth()); if (waveform_enable() && op_waveform(opoffs) != 0) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " wf=%u", op_waveform(opoffs)); + end += snprintf(&buffer[end], sizeof(buffer) - end, " wf=%u", op_waveform(opoffs)); if (is_rhythm(choffs)) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " rhy=1"); + end += snprintf(&buffer[end], sizeof(buffer) - end, " rhy=1"); if (DYNAMIC_OPS) { operator_mapping map; operator_map(map); if (bitfield(map.chan[chnum], 16, 8) != 0xff) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " 4op"); + end += snprintf(&buffer[end], sizeof(buffer) - end, " 4op"); } return buffer; @@ -685,9 +685,9 @@ std::string opll_registers::log_keyon(uint32_t choffs, uint32_t opoffs) uint32_t opnum = opoffs; char buffer[256]; - char *end = &buffer[0]; + int end = 0; - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, "%u.%02u freq=%04X inst=%X fb=%u mul=%X", + end += snprintf(&buffer[end], sizeof(buffer) - end, "%u.%02u freq=%04X inst=%X fb=%u mul=%X", chnum, opnum, ch_block_freq(choffs), ch_instrument(choffs), @@ -695,11 +695,11 @@ std::string opll_registers::log_keyon(uint32_t choffs, uint32_t opoffs) op_multiple(opoffs)); if (bitfield(opoffs, 0) == 1 || (is_rhythm(choffs) && choffs >= 6)) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " vol=%X", op_volume(opoffs)); + end += snprintf(&buffer[end], sizeof(buffer) - end, " vol=%X", op_volume(opoffs)); else - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " tl=%02X", ch_total_level(choffs)); + end += snprintf(&buffer[end], sizeof(buffer) - end, " tl=%02X", ch_total_level(choffs)); - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " ksr=%u ksl=%u adr=%X/%X/%X sl=%X sus=%u/%u", + end += snprintf(&buffer[end], sizeof(buffer) - end, " ksr=%u ksl=%u adr=%X/%X/%X sl=%X sus=%u/%u", op_ksr(opoffs), op_ksl(opoffs), op_attack_rate(opoffs), @@ -710,13 +710,13 @@ std::string opll_registers::log_keyon(uint32_t choffs, uint32_t opoffs) ch_sustain(choffs)); if (op_lfo_am_enable(opoffs)) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " am=1"); + end += snprintf(&buffer[end], sizeof(buffer) - end, " am=1"); if (op_lfo_pm_enable(opoffs)) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " pm=1"); + end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=1"); if (op_waveform(opoffs) != 0) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " wf=1"); + end += snprintf(&buffer[end], sizeof(buffer) - end, " wf=1"); if (is_rhythm(choffs)) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " rhy=1"); + end += snprintf(&buffer[end], sizeof(buffer) - end, " rhy=1"); return buffer; } diff --git a/src/sound/ymfm/ymfm_opm.cpp b/src/sound/ymfm/ymfm_opm.cpp index c72badb57a..03f54fb903 100644 --- a/src/sound/ymfm/ymfm_opm.cpp +++ b/src/sound/ymfm/ymfm_opm.cpp @@ -60,17 +60,17 @@ opm_registers::opm_registers() : { // waveform 0 is a sawtooth uint8_t am = index ^ 0xff; - int8_t pm = int8_t(index); + uint8_t pm = index; m_lfo_waveform[0][index] = am | (pm << 8); // waveform 1 is a square wave am = bitfield(index, 7) ? 0 : 0xff; - pm = int8_t(am ^ 0x80); + pm = am ^ 0x80; m_lfo_waveform[1][index] = am | (pm << 8); // waveform 2 is a triangle wave am = bitfield(index, 7) ? (index << 1) : ((index ^ 0xff) << 1); - pm = int8_t(bitfield(index, 6) ? am : ~am); + pm = bitfield(index, 6) ? am : ~am; m_lfo_waveform[2][index] = am | (pm << 8); // waveform 3 is noise; it is filled in dynamically @@ -330,7 +330,7 @@ uint32_t opm_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opd if (pm_sensitivity < 6) delta += lfo_raw_pm >> (6 - pm_sensitivity); else - delta += lfo_raw_pm << (pm_sensitivity - 5); + delta += uint32_t(lfo_raw_pm) << (pm_sensitivity - 5); } // apply delta and convert to a frequency number @@ -354,9 +354,9 @@ std::string opm_registers::log_keyon(uint32_t choffs, uint32_t opoffs) uint32_t opnum = opoffs; char buffer[256]; - char *end = &buffer[0]; + int end = 0; - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, "%u.%02u freq=%04X dt2=%u dt=%u fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c", + end += snprintf(&buffer[end], sizeof(buffer) - end, "%u.%02u freq=%04X dt2=%u dt=%u fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c", chnum, opnum, ch_block_freq(choffs), op_detune2(opoffs), @@ -376,14 +376,14 @@ std::string opm_registers::log_keyon(uint32_t choffs, uint32_t opoffs) bool am = (lfo_am_depth() != 0 && ch_lfo_am_sens(choffs) != 0 && op_lfo_am_enable(opoffs) != 0); if (am) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " am=%u/%02X", ch_lfo_am_sens(choffs), lfo_am_depth()); + end += snprintf(&buffer[end], sizeof(buffer) - end, " am=%u/%02X", ch_lfo_am_sens(choffs), lfo_am_depth()); bool pm = (lfo_pm_depth() != 0 && ch_lfo_pm_sens(choffs) != 0); if (pm) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " pm=%u/%02X", ch_lfo_pm_sens(choffs), lfo_pm_depth()); + end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=%u/%02X", ch_lfo_pm_sens(choffs), lfo_pm_depth()); if (am || pm) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " lfo=%02X/%c", lfo_rate(), "WQTN"[lfo_waveform()]); + end += snprintf(&buffer[end], sizeof(buffer) - end, " lfo=%02X/%c", lfo_rate(), "WQTN"[lfo_waveform()]); if (noise_enable() && opoffs == 31) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " noise=1"); + end += snprintf(&buffer[end], sizeof(buffer) - end, " noise=1"); return buffer; } diff --git a/src/sound/ymfm/ymfm_opn.cpp b/src/sound/ymfm/ymfm_opn.cpp index 388162dfef..16ca3416c6 100644 --- a/src/sound/ymfm/ymfm_opn.cpp +++ b/src/sound/ymfm/ymfm_opn.cpp @@ -409,9 +409,9 @@ std::string opn_registers_base::log_keyon(uint32_t choffs, uint32_t opof } char buffer[256]; - char *end = &buffer[0]; + int end = 0; - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, "%u.%02u freq=%04X dt=%u fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X", + end += snprintf(&buffer[end], sizeof(buffer) - end, "%u.%02u freq=%04X dt=%u fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X", chnum, opnum, block_freq, op_detune(opoffs), @@ -427,21 +427,21 @@ std::string opn_registers_base::log_keyon(uint32_t choffs, uint32_t opof op_sustain_level(opoffs)); if (OUTPUTS > 1) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " out=%c%c", + end += snprintf(&buffer[end], sizeof(buffer) - end, " out=%c%c", ch_output_0(choffs) ? 'L' : '-', ch_output_1(choffs) ? 'R' : '-'); if (op_ssg_eg_enable(opoffs)) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " ssg=%X", op_ssg_eg_mode(opoffs)); + end += snprintf(&buffer[end], sizeof(buffer) - end, " ssg=%X", op_ssg_eg_mode(opoffs)); bool am = (op_lfo_am_enable(opoffs) && ch_lfo_am_sens(choffs) != 0); if (am) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " am=%u", ch_lfo_am_sens(choffs)); + end += snprintf(&buffer[end], sizeof(buffer) - end, " am=%u", ch_lfo_am_sens(choffs)); bool pm = (ch_lfo_pm_sens(choffs) != 0); if (pm) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " pm=%u", ch_lfo_pm_sens(choffs)); + end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=%u", ch_lfo_pm_sens(choffs)); if (am || pm) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " lfo=%02X", lfo_rate()); + end += snprintf(&buffer[end], sizeof(buffer) - end, " lfo=%02X", lfo_rate()); if (multi_freq() && choffs == 2) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " multi=1"); + end += snprintf(&buffer[end], sizeof(buffer) - end, " multi=1"); return buffer; } diff --git a/src/sound/ymfm/ymfm_opq.cpp b/src/sound/ymfm/ymfm_opq.cpp index e6f6fa5ea4..78ae161640 100644 --- a/src/sound/ymfm/ymfm_opq.cpp +++ b/src/sound/ymfm/ymfm_opq.cpp @@ -339,9 +339,9 @@ std::string opq_registers::log_keyon(uint32_t choffs, uint32_t opoffs) uint32_t opnum = opoffs; char buffer[256]; - char *end = &buffer[0]; + int end = 0; - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, "%u.%02u freq=%04X dt=%+2d fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c", + end += snprintf(&buffer[end], sizeof(buffer) - end, "%u.%02u freq=%04X dt=%+2d fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c", chnum, opnum, (opoffs & 1) ? ch_block_freq_24(choffs) : ch_block_freq_13(choffs), int32_t(op_detune(opoffs)) - 0x20, @@ -360,14 +360,14 @@ std::string opq_registers::log_keyon(uint32_t choffs, uint32_t opoffs) bool am = (lfo_enable() && op_lfo_am_enable(opoffs) && ch_lfo_am_sens(choffs) != 0); if (am) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " am=%u", ch_lfo_am_sens(choffs)); + end += snprintf(&buffer[end], sizeof(buffer) - end, " am=%u", ch_lfo_am_sens(choffs)); bool pm = (lfo_enable() && ch_lfo_pm_sens(choffs) != 0); if (pm) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " pm=%u", ch_lfo_pm_sens(choffs)); + end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=%u", ch_lfo_pm_sens(choffs)); if (am || pm) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " lfo=%02X", lfo_rate()); + end += snprintf(&buffer[end], sizeof(buffer) - end, " lfo=%02X", lfo_rate()); if (ch_reverb(choffs)) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " reverb"); + end += snprintf(&buffer[end], sizeof(buffer) - end, " reverb"); return buffer; } diff --git a/src/sound/ymfm/ymfm_opz.cpp b/src/sound/ymfm/ymfm_opz.cpp index a5ec912aad..1178417bb8 100644 --- a/src/sound/ymfm/ymfm_opz.cpp +++ b/src/sound/ymfm/ymfm_opz.cpp @@ -129,17 +129,17 @@ opz_registers::opz_registers() : { // waveform 0 is a sawtooth uint8_t am = index ^ 0xff; - int8_t pm = int8_t(index); + uint8_t pm = index; m_lfo_waveform[0][index] = am | (pm << 8); // waveform 1 is a square wave am = bitfield(index, 7) ? 0 : 0xff; - pm = int8_t(am ^ 0x80); + pm = am ^ 0x80; m_lfo_waveform[1][index] = am | (pm << 8); // waveform 2 is a triangle wave am = bitfield(index, 7) ? (index << 1) : ((index ^ 0xff) << 1); - pm = int8_t(bitfield(index, 6) ? am : ~am); + pm = bitfield(index, 6) ? am : ~am; m_lfo_waveform[2][index] = am | (pm << 8); // waveform 3 is noise; it is filled in dynamically @@ -555,16 +555,16 @@ std::string opz_registers::log_keyon(uint32_t choffs, uint32_t opoffs) uint32_t opnum = opoffs; char buffer[256]; - char *end = &buffer[0]; + int end = 0; - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, "%u.%02u", chnum, opnum); + end += snprintf(&buffer[end], sizeof(buffer) - end, "%u.%02u", chnum, opnum); if (op_fix_mode(opoffs)) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " fixfreq=%X fine=%X shift=%X", op_fix_frequency(opoffs), op_fine(opoffs), op_fix_range(opoffs)); + end += snprintf(&buffer[end], sizeof(buffer) - end, " fixfreq=%X fine=%X shift=%X", op_fix_frequency(opoffs), op_fine(opoffs), op_fix_range(opoffs)); else - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " freq=%04X dt2=%u fine=%X", ch_block_freq(choffs), op_detune2(opoffs), op_fine(opoffs)); + end += snprintf(&buffer[end], sizeof(buffer) - end, " freq=%04X dt2=%u fine=%X", ch_block_freq(choffs), op_detune2(opoffs), op_fine(opoffs)); - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " dt=%u fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c", + end += snprintf(&buffer[end], sizeof(buffer) - end, " dt=%u fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c", op_detune(opoffs), ch_feedback(choffs), ch_algorithm(choffs), @@ -580,32 +580,32 @@ std::string opz_registers::log_keyon(uint32_t choffs, uint32_t opoffs) ch_output_1(choffs) ? 'R' : '-'); if (op_eg_shift(opoffs) != 0) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " egshift=%u", op_eg_shift(opoffs)); + end += snprintf(&buffer[end], sizeof(buffer) - end, " egshift=%u", op_eg_shift(opoffs)); bool am = (lfo_am_depth() != 0 && ch_lfo_am_sens(choffs) != 0 && op_lfo_am_enable(opoffs) != 0); if (am) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " am=%u/%02X", ch_lfo_am_sens(choffs), lfo_am_depth()); + end += snprintf(&buffer[end], sizeof(buffer) - end, " am=%u/%02X", ch_lfo_am_sens(choffs), lfo_am_depth()); bool pm = (lfo_pm_depth() != 0 && ch_lfo_pm_sens(choffs) != 0); if (pm) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " pm=%u/%02X", ch_lfo_pm_sens(choffs), lfo_pm_depth()); + end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=%u/%02X", ch_lfo_pm_sens(choffs), lfo_pm_depth()); if (am || pm) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " lfo=%02X/%c", lfo_rate(), "WQTN"[lfo_waveform()]); + end += snprintf(&buffer[end], sizeof(buffer) - end, " lfo=%02X/%c", lfo_rate(), "WQTN"[lfo_waveform()]); bool am2 = (lfo2_am_depth() != 0 && ch_lfo2_am_sens(choffs) != 0 && op_lfo_am_enable(opoffs) != 0); if (am2) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " am2=%u/%02X", ch_lfo2_am_sens(choffs), lfo2_am_depth()); + end += snprintf(&buffer[end], sizeof(buffer) - end, " am2=%u/%02X", ch_lfo2_am_sens(choffs), lfo2_am_depth()); bool pm2 = (lfo2_pm_depth() != 0 && ch_lfo2_pm_sens(choffs) != 0); if (pm2) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " pm2=%u/%02X", ch_lfo2_pm_sens(choffs), lfo2_pm_depth()); + end += snprintf(&buffer[end], sizeof(buffer) - end, " pm2=%u/%02X", ch_lfo2_pm_sens(choffs), lfo2_pm_depth()); if (am2 || pm2) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " lfo2=%02X/%c", lfo2_rate(), "WQTN"[lfo2_waveform()]); + end += snprintf(&buffer[end], sizeof(buffer) - end, " lfo2=%02X/%c", lfo2_rate(), "WQTN"[lfo2_waveform()]); if (op_reverb_rate(opoffs) != 0) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " rev=%u", op_reverb_rate(opoffs)); + end += snprintf(&buffer[end], sizeof(buffer) - end, " rev=%u", op_reverb_rate(opoffs)); if (op_waveform(opoffs) != 0) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " wf=%u", op_waveform(opoffs)); + end += snprintf(&buffer[end], sizeof(buffer) - end, " wf=%u", op_waveform(opoffs)); if (noise_enable() && opoffs == 31) - end += snprintf(end, SNPRINTF_BUFFER_SIZE_CALC, " noise=1"); + end += snprintf(&buffer[end], sizeof(buffer) - end, " noise=1"); return buffer; }