diff --git a/.ci/build.sh b/.ci/build.sh index 278e252d8..60917f43e 100644 --- a/.ci/build.sh +++ b/.ci/build.sh @@ -107,6 +107,19 @@ make_tar() { return $? } +cache_dir="$HOME/86box-build-cache" +[ ! -d "$cache_dir" ] && mkdir -p "$cache_dir" +check_buildtag() { + [ -z "$BUILD_TAG" -o "$BUILD_TAG" != "$(cat "$cache_dir/buildtag.$1" 2> /dev/null)" ] + return $? +} +save_buildtag() { + local contents="$BUILD_TAG" + [ -n "$2" ] && local contents="$2" + echo "$contents" > "$cache_dir/buildtag.$1" + return $? +} + # Set common variables. project=86Box cwd=$(pwd) @@ -244,105 +257,128 @@ then fi echo [-] Using MSYSTEM [$MSYSTEM] - # Update keyring, as the package signing keys sometimes change. - echo [-] Updating package databases and keyring - yes | pacman -Sy --needed msys2-keyring - - # Query installed packages. - pacman -Qe > pacman.txt - - # Download the specified versions of architecture-specific dependencies. - echo -n [-] Downloading dependencies: - pkg_dir="/var/cache/pacman/pkg" - repo_base="https://repo.msys2.org/mingw/$(echo $MSYSTEM | tr '[:upper:]' '[:lower:]')" - cat .ci/dependencies_msys.txt | tr -d '\r' > deps.txt - pkgs="" - while IFS=" " read pkg version - do - prefixed_pkg="$MINGW_PACKAGE_PREFIX-$pkg" - installed_version=$(grep -E "^$prefixed_pkg " pacman.txt | cut -d " " -f 2) - if [ "$installed_version" != "$version" ] # installed_version will be empty if not installed + # Install dependencies only if we're in a new build and/or architecture. + freetype_dll="$cache_dir/freetype.$MSYSTEM.dll" + if check_buildtag "$MSYSTEM" + then + # Update databases and keyring only if we're in a new build. + if check_buildtag pacmansync then - echo -n " [$pkg" + # Update keyring as well, since the package signing keys sometimes change. + echo [-] Updating package databases and keyring + yes | pacman -Sy --needed msys2-keyring - # Download package if not already present in the local cache. - pkg_tar="$prefixed_pkg-$version-any.pkg.tar" - if [ -s "$pkg_dir/$pkg_tar.xz" ] + # Save build tag to skip pacman sync/keyring later. + save_buildtag pacmansync + else + echo [-] Not updating package databases and keyring again + fi + + # Query installed packages. + pacman -Qe > "$cache_dir/pacman.txt" + + # Download the specified versions of architecture-specific dependencies. + echo -n [-] Downloading dependencies: + pkg_dir="/var/cache/pacman/pkg" + repo_base="https://repo.msys2.org/mingw/$(echo $MSYSTEM | tr '[:upper:]' '[:lower:]')" + cat .ci/dependencies_msys.txt | tr -d '\r' > "$cache_dir/deps.txt" + pkgs="" + while IFS=" " read pkg version + do + prefixed_pkg="$MINGW_PACKAGE_PREFIX-$pkg" + installed_version=$(grep -E "^$prefixed_pkg " "$cache_dir/pacman.txt" | cut -d " " -f 2) + if [ "$installed_version" != "$version" ] # installed_version will be empty if not installed then - pkg_fn="$pkg_tar.xz" - pkg_dest="$pkg_dir/$pkg_fn" - else - pkg_fn="$pkg_tar.zst" - pkg_dest="$pkg_dir/$pkg_fn" - if [ ! -s "$pkg_dest" ] + echo -n " [$pkg" + + # Download package if not already present in the local cache. + pkg_tar="$prefixed_pkg-$version-any.pkg.tar" + if [ -s "$pkg_dir/$pkg_tar.xz" ] then - if ! wget -qO "$pkg_dest" "$repo_base/$pkg_fn" + pkg_fn="$pkg_tar.xz" + pkg_dest="$pkg_dir/$pkg_fn" + else + pkg_fn="$pkg_tar.zst" + pkg_dest="$pkg_dir/$pkg_fn" + if [ ! -s "$pkg_dest" ] then - rm -f "$pkg_dest" - pkg_fn="$pkg_tar.xz" - pkg_dest="$pkg_dir/$pkg_fn" - wget -qO "$pkg_dest" "$repo_base/$pkg_fn" || rm -f "$pkg_dest" - fi - if [ -s "$pkg_dest" ] - then - wget -qO "$pkg_dest.sig" "$repo_base/$pkg_fn.sig" || rm -f "$pkg_dest.sig" - [ ! -s "$pkg_dest.sig" ] && rm -f "$pkg_dest.sig" + if ! wget -qO "$pkg_dest" "$repo_base/$pkg_fn" + then + rm -f "$pkg_dest" + pkg_fn="$pkg_tar.xz" + pkg_dest="$pkg_dir/$pkg_fn" + wget -qO "$pkg_dest" "$repo_base/$pkg_fn" || rm -f "$pkg_dest" + fi + if [ -s "$pkg_dest" ] + then + wget -qO "$pkg_dest.sig" "$repo_base/$pkg_fn.sig" || rm -f "$pkg_dest.sig" + [ ! -s "$pkg_dest.sig" ] && rm -f "$pkg_dest.sig" + fi fi fi - fi - # Check if the cached package is valid. - if [ -s "$pkg_dest" ] + # Check if the cached package is valid. + if [ -s "$pkg_dest" ] + then + # Add cached zst package. + pkgs="$pkgs $pkg_fn" + else + # Not valid, remove if it exists. + rm -f "$pkg_dest" "$pkg_dest.sig" + echo -n " FAIL" + fi + echo -n "]" + fi + done < "$cache_dir/deps.txt" + [ -z "$pkgs" ] && echo -n ' none required' + echo + + # Install the downloaded architecture-specific dependencies. + echo [-] Installing dependencies through pacman + if [ -n "$pkgs" ] + then + pushd "$pkg_dir" + yes | pacman -U --needed $pkgs + if [ $? -ne 0 ] then - # Add cached zst package. - pkgs="$pkgs $pkg_fn" - else - # Not valid, remove if it exists. - rm -f "$pkg_dest" "$pkg_dest.sig" - echo -n " FAIL" + # Install packages individually if installing them all together failed. + for pkg in $pkgs + do + yes | pacman -U --needed "$pkg" + done fi - echo -n "]" - fi - done < deps.txt - [ -z "$pkgs" ] && echo -n ' none required' - echo + popd - # Install the downloaded architecture-specific dependencies. - echo [-] Installing dependencies through pacman - if [ -n "$pkgs" ] - then - pushd "$pkg_dir" - yes | pacman -U --needed $pkgs + # Query installed packages again. + pacman -Qe > "$cache_dir/pacman.txt" + fi + + # Install the latest versions for any missing packages (if the specified version couldn't be installed). + pkgs="git" + while IFS=" " read pkg version + do + prefixed_pkg="$MINGW_PACKAGE_PREFIX-$pkg" + grep -qE "^$prefixed_pkg " "$cache_dir/pacman.txt" || pkgs="$pkgs $prefixed_pkg" + done < "$cache_dir/deps.txt" + rm -f "$cache_dir/pacman.txt" "$cache_dir/deps.txt" + yes | pacman -S --needed $pkgs if [ $? -ne 0 ] then # Install packages individually if installing them all together failed. for pkg in $pkgs do - yes | pacman -U --needed "$pkg" + yes | pacman -S --needed "$pkg" done fi - popd - # Query installed packages again. - pacman -Qe > pacman.txt - fi + # Generate a new freetype DLL for this architecture. + rm -f "$freetype_dll" - # Install the latest versions for any missing packages (if the specified version couldn't be installed). - pkgs="git" - while IFS=" " read pkg version - do - prefixed_pkg="$MINGW_PACKAGE_PREFIX-$pkg" - grep -qE "^$prefixed_pkg " pacman.txt || pkgs="$pkgs $prefixed_pkg" - done < deps.txt - rm -f pacman.txt deps.txt - yes | pacman -S --needed $pkgs - if [ $? -ne 0 ] - then - # Install packages individually if installing them all together failed. - for pkg in $pkgs - do - yes | pacman -S --needed "$pkg" - done + # Save build tag to skip this later. Doing it here (once everything is + # in place) is important to avoid potential issues with retried builds. + save_buildtag "$MSYSTEM" + else + echo [-] Not installing dependencies again fi # Point CMake to the toolchain file. @@ -352,7 +388,7 @@ then # macOS lacks nproc, but sysctl can do the same job. alias nproc='sysctl -n hw.logicalcpu' - # Handle universal build. + # Handle universal building. if echo "$arch" | grep -q '+' then # Create temporary directory for merging app bundles. @@ -391,18 +427,18 @@ then echo [-] Merging app bundles [$merge_src] and [$arch_universal] into [$merge_dest] # Merge directory structures. - (cd "archive_tmp_universal/$merge_src.app" && find . -type d && cd "../../archive_tmp_universal/$arch_universal.app" && find . -type d && cd ../..) | sort > universal_listing.txt - cat universal_listing.txt | uniq | while IFS= read line + (cd "archive_tmp_universal/$merge_src.app" && find . -type d && cd "../../archive_tmp_universal/$arch_universal.app" && find . -type d && cd ../..) | sort > "$cache_dir/universal_listing.txt" + cat "$cache_dir/universal_listing.txt" | uniq | while IFS= read line do echo "> Directory: $line" mkdir -p "archive_tmp_universal/$merge_dest.app/$line" done # Create merged file listing. - (cd "archive_tmp_universal/$merge_src.app" && find . -type f && cd "../../archive_tmp_universal/$arch_universal.app" && find . -type f && cd ../..) | sort > universal_listing.txt + (cd "archive_tmp_universal/$merge_src.app" && find . -type f && cd "../../archive_tmp_universal/$arch_universal.app" && find . -type f && cd ../..) | sort > "$cache_dir/universal_listing.txt" # Copy files that only exist on one bundle. - cat universal_listing.txt | uniq -u | while IFS= read line + cat "$cache_dir/universal_listing.txt" | uniq -u | while IFS= read line do if [ -e "archive_tmp_universal/$merge_src.app/$line" ] then @@ -415,7 +451,7 @@ then done # Copy or lipo files that exist on both bundles. - cat universal_listing.txt | uniq -d | while IFS= read line + cat "$cache_dir/universal_listing.txt" | uniq -d | while IFS= read line do if cmp -s "archive_tmp_universal/$merge_src.app/$line" "archive_tmp_universal/$arch_universal.app/$line" then @@ -431,8 +467,8 @@ then done # Merge symlinks. - (cd "archive_tmp_universal/$merge_src.app" && find . -type l && cd "../../archive_tmp_universal/$arch_universal.app" && find . -type l && cd ../..) | sort > universal_listing.txt - cat universal_listing.txt | uniq | while IFS= read line + (cd "archive_tmp_universal/$merge_src.app" && find . -type l && cd "../../archive_tmp_universal/$arch_universal.app" && find . -type l && cd ../..) | sort > "$cache_dir/universal_listing.txt" + cat "$cache_dir/universal_listing.txt" | uniq | while IFS= read line do # Get symlink destinations. other_link_dest= @@ -526,10 +562,21 @@ then [ "$arch" = "x86_64" -a -e "/opt/intel/bin/port" ] && macports="/opt/intel" export PATH="$macports/bin:$macports/sbin:$macports/libexec/qt5/bin:$PATH" - # Install dependencies. - echo [-] Installing dependencies through MacPorts - sudo "$macports/bin/port" selfupdate - sudo "$macports/bin/port" install $(cat .ci/dependencies_macports.txt) + # Install dependencies only if we're in a new build and/or architecture. + if check_buildtag "$(arch)" + then + # Install dependencies. + echo [-] Installing dependencies through MacPorts + sudo "$macports/bin/port" selfupdate + sudo "$macports/bin/port" install $(cat .ci/dependencies_macports.txt) + + # Save build tag to skip this later. Doing it here (once everything is + # in place) is important to avoid potential issues with retried builds. + save_buildtag "$(arch)" + else + echo [-] Not installing dependencies again + + fi # Point CMake to the toolchain file. [ -e "cmake/$toolchain.cmake" ] && cmake_flags_extra="$cmake_flags_extra -D \"CMAKE_TOOLCHAIN_FILE=cmake/$toolchain.cmake\"" @@ -543,17 +590,25 @@ else esac # Establish general dependencies. - pkgs="cmake ninja-build pkg-config git wget p7zip-full wayland-protocols tar gzip file appstream" + pkgs="cmake ninja-build pkg-config git wget p7zip-full extra-cmake-modules wayland-protocols tar gzip file appstream" if [ "$(dpkg --print-architecture)" = "$arch_deb" ] then pkgs="$pkgs build-essential" else - sudo dpkg --add-architecture "$arch_deb" + # Add foreign architecture if required. + if ! dpkg --print-foreign-architectures | grep -qE '^'"$arch_deb"'$' + then + sudo dpkg --add-architecture "$arch_deb" + + # Force an apt-get update. + save_buildtag aptupdate "arch_$arch_deb" + fi + pkgs="$pkgs crossbuild-essential-$arch_deb" fi # Establish architecture-specific dependencies we don't want listed on the readme... - pkgs="$pkgs linux-libc-dev:$arch_deb extra-cmake-modules:$arch_deb qttools5-dev:$arch_deb qtbase5-private-dev:$arch_deb" + pkgs="$pkgs linux-libc-dev:$arch_deb qttools5-dev:$arch_deb qtbase5-private-dev:$arch_deb" # ...and the ones we do want listed. Non-dev packages fill missing spots on the list. libpkgs="" @@ -606,11 +661,28 @@ EOF cmake_flags_extra="$cmake_flags_extra -D CMAKE_TOOLCHAIN_FILE=toolchain.cmake" strip_binary="$arch_triplet-strip" - # Install or update dependencies. - echo [-] Installing dependencies through apt - sudo apt-get update - DEBIAN_FRONTEND=noninteractive sudo apt-get -y install $pkgs $libpkgs - sudo apt-get clean + # Install dependencies only if we're in a new build and/or architecture. + if check_buildtag "$arch_deb" + then + # Install or update dependencies. + echo [-] Installing dependencies through apt + if check_buildtag aptupdate + then + sudo apt-get update + + # Save build tag to skip apt-get update later, unless a new architecture + # is added to dpkg, in which case, this saved tag file gets replaced. + save_buildtag aptupdate + fi + DEBIAN_FRONTEND=noninteractive sudo apt-get -y install $pkgs $libpkgs + sudo apt-get clean + + # Save build tag to skip this later. Doing it here (once everything is + # in place) is important to avoid potential issues with retried builds. + save_buildtag "$arch_deb" + else + echo [-] Not installing dependencies again + fi # Link against the system libslirp instead of compiling ours. cmake_flags_extra="$cmake_flags_extra -D SLIRP_EXTERNAL=ON" @@ -618,12 +690,7 @@ fi # Clean workspace. echo [-] Cleaning workspace -if [ -d "build" ] -then - cmake --build build -j$(nproc) --target clean 2> /dev/null - rm -rf build -fi -find . \( -name Makefile -o -name CMakeCache.txt -o -name CMakeFiles \) -exec rm -rf "{}" \; 2> /dev/null +rm -rf build # Add ARCH to skip the arch_detect process. case $arch in @@ -671,17 +738,25 @@ then exit 4 fi -# Download Discord Game SDK from their CDN if necessary. -if [ ! -e "discord_game_sdk.zip" ] +# Download Discord Game SDK from their CDN if we're in a new build. +discord_zip="$cache_dir/discord_game_sdk.zip" +if check_buildtag discord then + # Download file. echo [-] Downloading Discord Game SDK - wget -qO discord_game_sdk.zip "https://dl-game-sdk.discordapp.net/latest/discord_game_sdk.zip" + wget -qO "$discord_zip" "https://dl-game-sdk.discordapp.net/latest/discord_game_sdk.zip" status=$? if [ $status -ne 0 ] then echo [!] Discord Game SDK download failed with status [$status] - rm -f discord_game_sdk.zip + rm -f "$discord_zip" + else + # Save build tag to skip this later. Doing it here (once everything is + # in place) is important to avoid potential issues with retried builds. + save_buildtag discord fi +else + echo [-] Not downloading Discord Game SDK again fi # Determine Discord Game SDK architecture. @@ -713,8 +788,9 @@ then sevenzip="$pf/7-Zip/7z.exe" [ "$arch" = "32" -a -d "/c/Program Files (x86)" ] && pf="/c/Program Files (x86)" - # Archive freetype from local MSYS installation. - .ci/static2dll.sh -p freetype2 /$MSYSTEM/lib/libfreetype.a archive_tmp/freetype.dll + # Archive freetype from cache or generate it from local MSYS installation. + [ ! -e "$freetype_dll" ] && .ci/static2dll.sh -p freetype2 /$MSYSTEM/lib/libfreetype.a "$freetype_dll" + cp -p "$freetype_dll" archive_tmp/freetype.dll # Archive Ghostscript DLL from local official distribution installation. for gs in "$pf"/gs/gs*.*.* @@ -723,7 +799,7 @@ then done # Archive Discord Game SDK DLL. - "$sevenzip" e -y -o"archive_tmp" discord_game_sdk.zip "lib/$arch_discord/discord_game_sdk.dll" + "$sevenzip" e -y -o"archive_tmp" "$discord_zip" "lib/$arch_discord/discord_game_sdk.dll" [ ! -e "archive_tmp/discord_game_sdk.dll" ] && echo [!] No Discord Game SDK for architecture [$arch_discord] # Archive other DLLs from local directory. @@ -749,31 +825,29 @@ then if [ $status -eq 0 ] then # Archive Discord Game SDK library. - unzip -j discord_game_sdk.zip "lib/$arch_discord/discord_game_sdk.dylib" -d "archive_tmp/"*".app/Contents/Frameworks" + unzip -j "$discord_zip" "lib/$arch_discord/discord_game_sdk.dylib" -d "archive_tmp/"*".app/Contents/Frameworks" [ ! -e "archive_tmp/"*".app/Contents/Frameworks/discord_game_sdk.dylib" ] && echo [!] No Discord Game SDK for architecture [$arch_discord] # Sign app bundle, unless we're in an universal build. [ $skip_archive -eq 0 ] && codesign --force --deep -s - "archive_tmp/"*".app" fi else - cwd_root=$(pwd) - cache_dir="$HOME/86box-build-cache" - [ ! -d "$cache_dir" ] && mkdir -p "$cache_dir" + cwd_root="$(pwd)" + check_buildtag "libs.$arch_deb" if grep -q "OPENAL:BOOL=ON" build/CMakeCache.txt then # Build openal-soft 1.21.1 manually to fix audio issues. This is a temporary # workaround until a newer version of openal-soft trickles down to Debian repos. prefix="$cache_dir/openal-soft-1.21.1" - if [ -d "$prefix" ] + if [ ! -d "$prefix" ] then - rm -rf "$prefix/build" - else wget -qO - https://github.com/kcat/openal-soft/archive/refs/tags/1.21.1.tar.gz | tar zxf - -C "$cache_dir" || rm -rf "$prefix" fi - cmake -G Ninja -D "CMAKE_TOOLCHAIN_FILE=$cwd_root/toolchain.cmake" -D "CMAKE_INSTALL_PREFIX=$cwd_root/archive_tmp/usr" -S "$prefix" -B "$prefix/build" || exit 99 - cmake --build "$prefix/build" -j$(nproc) || exit 99 - cmake --install "$prefix/build" || exit 99 + prefix_build="$prefix/build-$arch_deb" + cmake -G Ninja -D "CMAKE_TOOLCHAIN_FILE=$cwd_root/toolchain.cmake" -D "CMAKE_INSTALL_PREFIX=$cwd_root/archive_tmp/usr" -S "$prefix" -B "$prefix_build" || exit 99 + cmake --build "$prefix_build" -j$(nproc) || exit 99 + cmake --install "$prefix_build" || exit 99 # Build SDL2 without sound systems. sdl_ss=OFF @@ -781,15 +855,14 @@ else # Build FAudio 22.03 manually to remove the dependency on GStreamer. This is a temporary # workaround until a newer version of FAudio trickles down to Debian repos. prefix="$cache_dir/FAudio-22.03" - if [ -d "$prefix" ] + if [ ! -d "$prefix" ] then - rm -rf "$prefix/build" - else wget -qO - https://github.com/FNA-XNA/FAudio/archive/refs/tags/22.03.tar.gz | tar zxf - -C "$cache_dir" || rm -rf "$prefix" fi - cmake -G Ninja -D "CMAKE_TOOLCHAIN_FILE=$cwd_root/toolchain.cmake" -D "CMAKE_INSTALL_PREFIX=$cwd_root/archive_tmp/usr" -S "$prefix" -B "$prefix/build" || exit 99 - cmake --build "$prefix/build" -j$(nproc) || exit 99 - cmake --install "$prefix/build" || exit 99 + prefix_build="$prefix/build-$arch_deb" + cmake -G Ninja -D "CMAKE_TOOLCHAIN_FILE=$cwd_root/toolchain.cmake" -D "CMAKE_INSTALL_PREFIX=$cwd_root/archive_tmp/usr" -S "$prefix" -B "$prefix_build" || exit 99 + cmake --build "$prefix_build" -j$(nproc) || exit 99 + cmake --install "$prefix_build" || exit 99 # Build SDL2 with sound systems. sdl_ss=ON @@ -801,15 +874,14 @@ else # Build rtmidi without JACK support to remove the dependency on libjack. prefix="$cache_dir/rtmidi-4.0.0" - if [ -d "$prefix" ] + if [ ! -d "$prefix" ] then - rm -rf "$prefix/build" - else wget -qO - https://github.com/thestk/rtmidi/archive/refs/tags/4.0.0.tar.gz | tar zxf - -C "$cache_dir" || rm -rf "$prefix" fi - cmake -G Ninja -D RTMIDI_API_JACK=OFF -D "CMAKE_TOOLCHAIN_FILE=$cwd_root/toolchain.cmake" -D "CMAKE_INSTALL_PREFIX=$cwd_root/archive_tmp/usr" -S "$prefix" -B "$prefix/build" || exit 99 - cmake --build "$prefix/build" -j$(nproc) || exit 99 - cmake --install "$prefix/build" || exit 99 + prefix_build="$prefix/build-$arch_deb" + cmake -G Ninja -D RTMIDI_API_JACK=OFF -D "CMAKE_TOOLCHAIN_FILE=$cwd_root/toolchain.cmake" -D "CMAKE_INSTALL_PREFIX=$cwd_root/archive_tmp/usr" -S "$prefix" -B "$prefix_build" || exit 99 + cmake --build "$prefix_build" -j$(nproc) || exit 99 + cmake --install "$prefix_build" || exit 99 # Build SDL2 for joystick and FAudio support, with most components # disabled to remove the dependencies on PulseAudio and libdrm. @@ -818,7 +890,7 @@ else then wget -qO - https://www.libsdl.org/release/SDL2-2.0.20.tar.gz | tar zxf - -C "$cache_dir" || rm -rf "$prefix" fi - rm -rf "$cache_dir/sdlbuild" + prefix_build="$cache_dir/SDL2-2.0.20-build-$arch_deb" cmake -G Ninja -D SDL_SHARED=ON -D SDL_STATIC=OFF \ \ -D SDL_AUDIO=$sdl_ss -D SDL_DUMMYAUDIO=$sdl_ss -D SDL_DISKAUDIO=OFF -D SDL_OSS=OFF -D SDL_ALSA=$sdl_ss -D SDL_ALSA_SHARED=$sdl_ss \ @@ -837,12 +909,12 @@ else -D SDL_LOADSO=ON -D SDL_CPUINFO=ON -D SDL_FILESYSTEM=$sdl_ui -D SDL_DLOPEN=OFF -D SDL_SENSOR=OFF -D SDL_LOCALE=OFF \ \ -D "CMAKE_TOOLCHAIN_FILE=$cwd_root/toolchain.cmake" -D "CMAKE_INSTALL_PREFIX=$cwd_root/archive_tmp/usr" \ - -S "$prefix" -B "$cache_dir/sdlbuild" || exit 99 - cmake --build "$cache_dir/sdlbuild" -j$(nproc) || exit 99 - cmake --install "$cache_dir/sdlbuild" || exit 99 + -S "$prefix" -B "$prefix_build" || exit 99 + cmake --build "$prefix_build" -j$(nproc) || exit 99 + cmake --install "$prefix_build" || exit 99 # Archive Discord Game SDK library. - 7z e -y -o"archive_tmp/usr/lib" discord_game_sdk.zip "lib/$arch_discord/discord_game_sdk.so" + 7z e -y -o"archive_tmp/usr/lib" "$discord_zip" "lib/$arch_discord/discord_game_sdk.so" [ ! -e "archive_tmp/usr/lib/discord_game_sdk.so" ] && echo [!] No Discord Game SDK for architecture [$arch_discord] # Archive readme with library package versions. diff --git a/.gitignore b/.gitignore index 5b1c5bbd1..267f3d766 100644 --- a/.gitignore +++ b/.gitignore @@ -28,9 +28,6 @@ Makefile /archive_tmp /archive_tmp_universal /static2dll.* -/pacman.txt -/deps.txt -/universal_listing.txt /VERSION *.zip *.tar diff --git a/CMakeLists.txt b/CMakeLists.txt index f9e856faf..561e1eeda 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,7 +118,7 @@ if(WIN32) endif() set(CMAKE_C_STANDARD 11) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 14) set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON) # Optional features diff --git a/src/86box.c b/src/86box.c index 805cc60bf..f6b791901 100644 --- a/src/86box.c +++ b/src/86box.c @@ -182,6 +182,8 @@ int confirm_reset = 1; /* (C) enable reset confirmation */ int confirm_exit = 1; /* (C) enable exit confirmation */ int confirm_save = 1; /* (C) enable save confirmation */ int enable_discord = 0; /* (C) enable Discord integration */ +int pit_mode = -1; /* (C) force setting PIT mode */ +int fm_driver = 0; /* (C) select FM sound driver */ /* Statistics. */ extern int mmuflush; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d1da45172..1420aaa89 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,7 +16,7 @@ # add_executable(86Box 86box.c config.c log.c random.c timer.c io.c acpi.c apm.c - dma.c ddma.c discord.c nmi.c pic.c pit.c port_6x.c port_92.c ppi.c pci.c + dma.c ddma.c discord.c nmi.c pic.c pit.c pit_fast.c port_6x.c port_92.c ppi.c pci.c mca.c usb.c fifo8.c device.c nvr.c nvr_at.c nvr_ps2.c machine_status.c) if(CMAKE_SYSTEM_NAME MATCHES "Linux") diff --git a/src/chipset/ali1543.c b/src/chipset/ali1543.c index bde561c09..bbbf7d705 100644 --- a/src/chipset/ali1543.c +++ b/src/chipset/ali1543.c @@ -289,7 +289,7 @@ ali1533_write(int func, int addr, uint8_t val, void *priv) break; } pci_relocate_slot(PCI_CARD_SOUTHBRIDGE_IDE, ((int) dev->ide_slot) + dev->offset); - ali1543_log("IDE slot = %02X (A%0i)\n", dev->ide_slot/* - 5*/, dev->ide_slot + 11); + ali1543_log("IDE slot = %02X (A%0i)\n", ((int) dev->ide_slot) + dev->offset, dev->ide_slot + 11); ali5229_ide_irq_handler(dev); break; @@ -359,7 +359,7 @@ ali1533_write(int func, int addr, uint8_t val, void *priv) break; } pci_relocate_slot(PCI_CARD_SOUTHBRIDGE_PMU, ((int) dev->pmu_slot) + dev->offset); - ali1543_log("PMU slot = %02X (A%0i)\n", dev->pmu_slot/* - 5*/, dev->pmu_slot + 11); + ali1543_log("PMU slot = %02X (A%0i)\n", ((int) dev->pmu_slot) + dev->offset, dev->pmu_slot + 11); switch (val & 0x03) { case 0x00: dev->usb_slot = 0x14; /* A31 = slot 20 */ @@ -375,7 +375,7 @@ ali1533_write(int func, int addr, uint8_t val, void *priv) break; } pci_relocate_slot(PCI_CARD_SOUTHBRIDGE_USB, ((int) dev->usb_slot) + dev->offset); - ali1543_log("USB slot = %02X (A%0i)\n", dev->usb_slot/* - 5*/, dev->usb_slot + 11); + ali1543_log("USB slot = %02X (A%0i)\n", ((int) dev->usb_slot) + dev->offset, dev->usb_slot + 11); break; case 0x73: /* DDMA Base Address */ @@ -1548,13 +1548,11 @@ ali1543_init(const device_t *info) /* USB */ dev->usb = device_add(&usb_device); - /* Super I/O chip */ - device_add(&ali5123_device); - dev->type = info->local & 0xff; dev->offset = (info->local >> 8) & 0x7f; if (info->local & 0x8000) dev->offset = -dev->offset; + pclog("Offset = %i\n", dev->offset); pci_enable_mirq(0); pci_enable_mirq(1); @@ -1564,6 +1562,9 @@ ali1543_init(const device_t *info) pci_enable_mirq(5); pci_enable_mirq(6); + /* Super I/O chip */ + device_add(&ali5123_device); + ali1543_reset(dev); return dev; diff --git a/src/chipset/via_pipc.c b/src/chipset/via_pipc.c index bcb54130f..b8c06b9f4 100644 --- a/src/chipset/via_pipc.c +++ b/src/chipset/via_pipc.c @@ -760,7 +760,7 @@ pipc_fm_read(uint16_t addr, void *priv) uint8_t ret = 0x00; #else pipc_t *dev = (pipc_t *) priv; - uint8_t ret = opl3_read(addr, &dev->sb->opl); + uint8_t ret = dev->sb->opl.read(addr, dev->sb->opl.priv); #endif pipc_log("PIPC: fm_read(%02X) = %02X\n", addr & 0x03, ret); @@ -794,7 +794,7 @@ pipc_fm_write(uint16_t addr, uint8_t val, void *priv) } } #else - opl3_write(addr, val, &dev->sb->opl); + dev->sb->opl.write(addr, val, dev->sb->opl.priv); #endif } @@ -807,22 +807,22 @@ pipc_sb_handlers(pipc_t *dev, uint8_t modem) sb_dsp_setaddr(&dev->sb->dsp, 0); if (dev->sb_base) { - io_removehandler(dev->sb_base, 4, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &dev->sb->opl); - io_removehandler(dev->sb_base + 8, 2, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &dev->sb->opl); + io_removehandler(dev->sb_base, 4, dev->sb->opl.read, NULL, NULL, dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); + io_removehandler(dev->sb_base + 8, 2, dev->sb->opl.read, NULL, NULL, dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); io_removehandler(dev->sb_base + 4, 2, sb_ct1345_mixer_read, NULL, NULL, sb_ct1345_mixer_write, NULL, NULL, dev->sb); } mpu401_change_addr(dev->sb->mpu, 0); mpu401_setirq(dev->sb->mpu, 0); - io_removehandler(0x388, 4, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &dev->sb->opl); + io_removehandler(0x388, 4, dev->sb->opl.read, NULL, NULL, dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); if (dev->ac97_regs[0][0x42] & 0x01) { dev->sb_base = 0x220 + (0x20 * (dev->ac97_regs[0][0x43] & 0x03)); sb_dsp_setaddr(&dev->sb->dsp, dev->sb_base); if (dev->ac97_regs[0][0x42] & 0x04) { - io_sethandler(dev->sb_base, 4, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &dev->sb->opl); - io_sethandler(dev->sb_base + 8, 2, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &dev->sb->opl); + io_sethandler(dev->sb_base, 4, dev->sb->opl.read, NULL, NULL, dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); + io_sethandler(dev->sb_base + 8, 2, dev->sb->opl.read, NULL, NULL, dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); } io_sethandler(dev->sb_base + 4, 2, sb_ct1345_mixer_read, NULL, NULL, sb_ct1345_mixer_write, NULL, NULL, dev->sb); diff --git a/src/chipset/wd76c10.c b/src/chipset/wd76c10.c index 138349955..12b7e19a0 100644 --- a/src/chipset/wd76c10.c +++ b/src/chipset/wd76c10.c @@ -45,8 +45,6 @@ #define LOCK dev->lock #define UNLOCKED !dev->lock -#define ENABLE_WD76C10_LOG 1 - #ifdef ENABLE_WD76C10_LOG int wd76c10_do_log = ENABLE_WD76C10_LOG; static void diff --git a/src/config.c b/src/config.c index c44c69d4a..81bbc016f 100644 --- a/src/config.c +++ b/src/config.c @@ -68,6 +68,7 @@ #include <86box/plat.h> #include <86box/plat_dir.h> #include <86box/ui.h> +#include <86box/snd_opl.h> typedef struct _list_ { @@ -886,6 +887,8 @@ load_machine(void) } else time_sync = !!config_get_int(cat, "enable_sync", 1); + pit_mode = config_get_int(cat, "pit_mode", -1); + /* Remove this after a while.. */ config_delete_var(cat, "nvr_path"); config_delete_var(cat, "enable_sync"); @@ -1108,6 +1111,13 @@ load_sound(void) sound_is_float = 1; else sound_is_float = 0; + + p = config_get_string(cat, "fm_driver", "nuked"); + if (!strcmp(p, "ymfm")) { + fm_driver = FM_DRV_YMFM; + } else { + fm_driver = FM_DRV_NUKED; + } } /* Load "Network" section. */ @@ -2479,6 +2489,11 @@ save_machine(void) else config_set_string(cat, "time_sync", "disabled"); + if (pit_mode == -1) + config_delete_var(cat, "pit_mode"); + else + config_set_int(cat, "pit_mode", pit_mode); + delete_section_if_empty(cat); } @@ -2623,6 +2638,8 @@ save_sound(void) else config_set_string(cat, "sound_type", (sound_is_float == 1) ? "float" : "int16"); + config_set_string(cat, "fm_driver", (fm_driver == FM_DRV_NUKED) ? "nuked" : "ymfm"); + delete_section_if_empty(cat); } diff --git a/src/cpu/x86_ops_misc.h b/src/cpu/x86_ops_misc.h index 50da6f2a0..7fde5dacc 100644 --- a/src/cpu/x86_ops_misc.h +++ b/src/cpu/x86_ops_misc.h @@ -259,7 +259,7 @@ static int opF6_a32(uint32_t fetchdat) static int opF7_w_a16(uint32_t fetchdat) { - uint32_t templ, templ2; + uint32_t templ, templ2 = 0; int tempws, tempws2 = 0; int16_t temps16; uint16_t src, dst; @@ -356,7 +356,7 @@ static int opF7_w_a16(uint32_t fetchdat) } static int opF7_w_a32(uint32_t fetchdat) { - uint32_t templ, templ2; + uint32_t templ, templ2 = 0; int tempws, tempws2 = 1; int16_t temps16; uint16_t src, dst; diff --git a/src/device/keyboard_at.c b/src/device/keyboard_at.c index 087fa2b96..8bb436a0c 100644 --- a/src/device/keyboard_at.c +++ b/src/device/keyboard_at.c @@ -89,6 +89,7 @@ #define KBC_VEN_OLIVETTI 0x24 #define KBC_VEN_NCR 0x28 #define KBC_VEN_SAMSUNG 0x2c +#define KBC_VEN_ALI 0x30 #define KBC_VEN_MASK 0x3c @@ -1093,6 +1094,8 @@ write_output(atkbd_t *dev, uint8_t val) softresetx86(); /*Pulse reset!*/ cpu_set_edx(); flushmmucache(); + if (kbc_ven == KBC_VEN_ALI) + smbase = 0x00030000; } } @@ -1353,6 +1356,7 @@ static uint8_t write64_ami(void *priv, uint8_t val) { atkbd_t *dev = (atkbd_t *)priv; + uint8_t kbc_ven = dev->flags & KBC_VEN_MASK; switch (val) { case 0x00: case 0x01: case 0x02: case 0x03: @@ -1386,7 +1390,15 @@ write64_ami(void *priv, uint8_t val) case 0xa1: /* get controller version */ kbd_log("ATkbc: AMI - get controller version\n"); - add_data(dev, 'H'); + if ((dev->flags & KBC_TYPE_MASK) >= KBC_TYPE_PS2_NOREF) { + if (kbc_ven == KBC_VEN_ALI) + add_data(dev, 'F'); + else if ((dev->flags & KBC_VEN_MASK) == KBC_VEN_INTEL_AMI) + add_data(dev, '5'); + else + add_data(dev, 'H'); + } else + add_data(dev, 'F'); return 0; case 0xa2: /* clear keyboard controller lines P22/P23 */ @@ -1456,9 +1468,14 @@ write64_ami(void *priv, uint8_t val) break; case 0xaf: /* set extended controller RAM */ - kbd_log("ATkbc: set extended controller RAM\n"); - dev->want60 = 1; - dev->secr_phase = 1; + if (kbc_ven == KBC_VEN_ALI) { + kbd_log("ATkbc: Award/ALi/VIA keyboard controller revision\n"); + add_to_kbc_queue_front(dev, 0x43, 0, 0x00); + } else { + kbd_log("ATkbc: set extended controller RAM\n"); + dev->want60 = 1; + dev->secr_phase = 1; + } return 0; case 0xb0: case 0xb1: case 0xb2: case 0xb3: @@ -1754,8 +1771,7 @@ kbd_write(uint16_t port, uint8_t val, void *priv) { atkbd_t *dev = (atkbd_t *)priv; int i = 0, bad = 1; - uint8_t mask, kbc_ven = 0x0; - kbc_ven = dev->flags & KBC_VEN_MASK; + uint8_t mask, kbc_ven = dev->flags & KBC_VEN_MASK; switch (port) { case 0x60: @@ -2315,6 +2331,7 @@ kbd_init(const device_t *info) case KBC_VEN_AMI: case KBC_VEN_INTEL_AMI: case KBC_VEN_SAMSUNG: + case KBC_VEN_ALI: dev->write60_ven = write60_ami; dev->write64_ven = write64_ami; break; @@ -2592,6 +2609,20 @@ const device_t keyboard_ps2_ami_pci_device = { .config = NULL }; +const device_t keyboard_ps2_ali_pci_device = { + .name = "PS/2 Keyboard (ALi M5123/M1543C)", + .internal_name = "keyboard_ps2_ali_pci", + .flags = DEVICE_PCI, + .local = KBC_TYPE_PS2_NOREF | KBC_VEN_ALI, + .init = kbd_init, + .close = kbd_close, + .reset = kbd_reset, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + const device_t keyboard_ps2_intel_ami_pci_device = { .name = "PS/2 Keyboard (AMI)", .internal_name = "keyboard_ps2_intel_ami_pci", diff --git a/src/device/keyboard_xt.c b/src/device/keyboard_xt.c index 9b19669e2..d13bab56d 100644 --- a/src/device/keyboard_xt.c +++ b/src/device/keyboard_xt.c @@ -535,7 +535,7 @@ kbd_write(uint16_t port, uint8_t val, void *priv) if (speaker_enable) was_speaker_enable = 1; - pit_ctr_set_gate(&pit->counters[2], val & 1); + pit_devs[0].set_gate(pit_devs[0].data, 2, val & 1); if (val & 0x80) { kbd->pa = 0; diff --git a/src/disk/hdc_ide.c b/src/disk/hdc_ide.c index 55f60c8e3..b49e2e926 100644 --- a/src/disk/hdc_ide.c +++ b/src/disk/hdc_ide.c @@ -1682,8 +1682,7 @@ ide_writeb(uint16_t addr, uint8_t val, void *priv) return; case WIN_WRITE_MULTIPLE: - if (!ide->blocksize && (ide->type != IDE_ATAPI)) - fatal("Write_MULTIPLE - blocksize = 0\n"); + /* Fatal removed for the same reason as for WIN_READ_MULTIPLE. */ ide->blockcount = 0; /* Turn on the activity indicator *here* so that it gets turned on less times. */ @@ -2410,7 +2409,13 @@ ide_callback(void *priv) return; case WIN_WRITE_MULTIPLE: - if (ide->type == IDE_ATAPI) + /* According to the official ATA reference: + + If the Read Multiple command is attempted before the Set Multiple Mode + command has been executed or when Read Multiple commands are + disabled, the Read Multiple operation is rejected with an Aborted Com- + mand error. */ + if ((ide->type == IDE_ATAPI) || !ide->blocksize) goto abort_cmd; if (!ide->lba && (ide->cfg_spt == 0)) goto id_not_found; diff --git a/src/dma.c b/src/dma.c index 8dfabd4fe..595f8cf64 100644 --- a/src/dma.c +++ b/src/dma.c @@ -62,6 +62,7 @@ static struct { #define DMA_PS2_IOA (1 << 0) +#define DMA_PS2_AUTOINIT (1 << 1) #define DMA_PS2_XFER_MEM_TO_IO (1 << 2) #define DMA_PS2_XFER_IO_TO_MEM (3 << 2) #define DMA_PS2_XFER_MASK (3 << 2) @@ -729,6 +730,8 @@ dma_ps2_write(uint16_t addr, uint8_t val, void *priv) else if ((val & DMA_PS2_XFER_MASK) == DMA_PS2_XFER_IO_TO_MEM) mode |= 4; dma_c->mode = (dma_c->mode & ~0x2c) | mode; + if (val & DMA_PS2_AUTOINIT) + dma_c->mode |= 0x10; dma_c->ps2_mode = val; dma_c->size = val & DMA_PS2_SIZE16; break; diff --git a/src/include/86box/86box.h b/src/include/86box/86box.h index 6afa66695..3d2806ce7 100644 --- a/src/include/86box/86box.h +++ b/src/include/86box/86box.h @@ -135,7 +135,8 @@ extern int is_pentium; /* TODO: Move back to cpu/cpu.h when it's figured out, how to remove that hack from the ET4000/W32p. */ extern int fixed_size_x, fixed_size_y; extern double mouse_sensitivity; /* (C) Mouse sensitivity scale */ - +extern int pit_mode; /* (C) force setting PIT mode */ +extern int fm_driver; /* (C) select FM sound driver */ extern char exe_path[2048]; /* path (dir) of executable */ extern char usr_path[1024]; /* path (dir) of user data */ diff --git a/src/include/86box/keyboard.h b/src/include/86box/keyboard.h index c07fe6a8b..29ea8e5fb 100644 --- a/src/include/86box/keyboard.h +++ b/src/include/86box/keyboard.h @@ -178,6 +178,7 @@ extern const device_t keyboard_ps2_pci_device; extern const device_t keyboard_ps2_ami_pci_device; extern const device_t keyboard_ps2_intel_ami_pci_device; extern const device_t keyboard_ps2_acer_pci_device; +extern const device_t keyboard_ps2_ali_pci_device; #endif extern void keyboard_init(void); diff --git a/src/include/86box/net_wd8003.h b/src/include/86box/net_wd8003.h index 8fc89a88b..08bd901fe 100644 --- a/src/include/86box/net_wd8003.h +++ b/src/include/86box/net_wd8003.h @@ -50,7 +50,8 @@ enum { WD8003EB, /* WD8003EB : 8-bit ISA, 5x3 interface chip */ WD8013EBT, /* WD8013EBT : 16-bit ISA, no interface chip */ WD8003ETA, /* WD8003ET/A: 16-bit MCA, no interface chip */ - WD8003EA /* WD8003E/A : 16-bit MCA, 5x3 interface chip */ + WD8003EA, /* WD8003E/A : 16-bit MCA, 5x3 interface chip */ + WD8013EPA }; extern const device_t wd8003e_device; @@ -58,5 +59,6 @@ extern const device_t wd8003eb_device; extern const device_t wd8013ebt_device; extern const device_t wd8003eta_device; extern const device_t wd8003ea_device; +extern const device_t wd8013epa_device; #endif /*NET_WD8003_H*/ diff --git a/src/include/86box/pit.h b/src/include/86box/pit.h index aaaa1448c..f40210ac1 100644 --- a/src/include/86box/pit.h +++ b/src/include/86box/pit.h @@ -58,9 +58,33 @@ typedef struct PIT { uint8_t ctrl; } pit_t; +enum { + PIT_8253 = 0, + PIT_8254, + PIT_8253_FAST, + PIT_8254_FAST +}; + +typedef struct { + uint8_t (*read)(uint16_t addr, void *priv); + void (*write)(uint16_t addr, uint8_t val, void *priv); + /* Gets a counter's count. */ + uint16_t (*get_count)(void *data, int counter_id); + /* Sets a counter's GATE input. */ + void (*set_gate)(void *data, int counter_id, int gate); + /* Sets if a counter's CLOCK input is from the timer or not - used by PCjr. */ + void(*set_using_timer)(void *data, int counter_id, int using_timer); + /* Sets a counter's OUT output handler. */ + void (*set_out_func)(void *data, int counter_id, void (*func)(int new_out, int old_out)); + /* Sets a counter's load count handler. */ + void (*set_load_func)(void *data, int counter_id, void (*func)(uint8_t new_m, int new_count)); + void (*ctr_clock)(void *data, int counter_id); + void *data; +} pit_intf_t; + +extern pit_intf_t pit_devs[2]; +extern const pit_intf_t pit_classic_intf; -extern pit_t *pit, - *pit2; extern double SYSCLK, PCICLK, AGPCLK; @@ -75,26 +99,13 @@ extern uint64_t PITCONST, ISACONST, extern int refresh_at_enable; - -/* Gets a counter's count. */ -extern uint16_t pit_ctr_get_count(ctr_t *ctr); -/* Sets a counter's load count handler. */ -extern void pit_ctr_set_load_func(ctr_t *ctr, void (*func)(uint8_t new_m, int new_count)); -/* Sets a counter's OUT output handler. */ -extern void pit_ctr_set_out_func(ctr_t *ctr, void (*func)(int new_out, int old_out)); -/* Sets a counter's GATE input. */ -extern void pit_ctr_set_gate(ctr_t *ctr, int gate); /* Sets a counter's CLOCK input. */ extern void pit_ctr_set_clock(ctr_t *ctr, int clock); -/* Sets if a counter's CLOCK input is from the timer or not - used by PCjr. */ -extern void pit_ctr_set_using_timer(ctr_t *ctr, int using_timer); extern pit_t * pit_common_init(int type, void (*out0)(int new_out, int old_out), void (*out1)(int new_out, int old_out)); -extern pit_t * pit_ps2_init(void); +extern pit_t * pit_ps2_init(int type); extern void pit_reset(pit_t *dev); -extern void pit_irq0_timer(int new_out, int old_out); -extern void pit_irq0_timer_pcjr(int new_out, int old_out); extern void pit_irq0_timer_ps2(int new_out, int old_out); extern void pit_refresh_timer_xt(int new_out, int old_out); diff --git a/src/include/86box/pit_fast.h b/src/include/86box/pit_fast.h new file mode 100644 index 000000000..bc09174fb --- /dev/null +++ b/src/include/86box/pit_fast.h @@ -0,0 +1,72 @@ +/* + * 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. + * + * Header of the implementation of the Intel 8253/8254 + * Programmable Interval Timer. + * + * + * + * Author: Miran Grca, + * Copyright 2019,2020 Miran Grca. + */ + +#ifndef EMU_PIT_FAST_H +#define EMU_PIT_FAST_H + +typedef struct { + uint8_t m, ctrl, + read_status, latch, bcd; + + uint16_t rl; + + int rm, wm, gate, out, + newcount, clock, using_timer, latched, + do_read_status; + int enabled; + int disabled; + int initial; + int thit; + int running; + int rereadlatch; + + union { + int count; + struct { + int units : 4; + int tens : 4; + int hundreds : 4; + int thousands : 4; + int myriads : 4; + }; + }; + + uint32_t l; + pc_timer_t timer; + + void (*load_func)(uint8_t new_m, int new_count); + void (*out_func)(int new_out, int old_out); +} ctrf_t; + +typedef struct { + int flags; + ctrf_t counters[3]; + + uint8_t ctrl; +} pitf_t; + +extern const pit_intf_t pit_fast_intf; + +#ifdef EMU_DEVICE_H +extern const device_t i8253_fast_device; +extern const device_t i8254_fast_device; +extern const device_t i8254_sec_fast_device; +extern const device_t i8254_ext_io_fast_device; +extern const device_t i8254_ps2_fast_device; +#endif + +#endif /*EMU_PIT_FAST_H*/ diff --git a/src/include/86box/snd_opl.h b/src/include/86box/snd_opl.h index ac8eef6fd..a2e8dd521 100644 --- a/src/include/86box/snd_opl.h +++ b/src/include/86box/snd_opl.h @@ -17,38 +17,40 @@ #ifndef SOUND_OPL_H #define SOUND_OPL_H -typedef void (*tmrfunc)(void *priv, int timer, uint64_t period); +enum fm_type { + FM_YM3812 = 0, + FM_YMF262, + FM_YMF289B, + FM_MAX +}; + +enum fm_driver { + FM_DRV_NUKED = 0, + FM_DRV_YMFM, + FM_DRV_MAX +}; -/* Define an OPLx chip. */ typedef struct { -#ifdef SOUND_OPL_NUKED_H - nuked_t *opl; -#else - void *opl; + uint8_t (*read)(uint16_t port, void *priv); + void (*write)(uint16_t port, uint8_t val, void *priv); + int32_t * (*update)(void *priv); + void (*reset_buffer)(void *priv); + void (*set_do_cycles)(void *priv, int8_t do_cycles); + void *priv; +} fm_drv_t; + +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; + +#ifdef EMU_DEVICE_H +extern const device_t ym3812_nuked_device; +extern const device_t ymf262_nuked_device; + +extern const device_t ym3812_ymfm_device; +extern const device_t ymf262_ymfm_device; +extern const device_t ymf289b_ymfm_device; #endif - int8_t flags, pad; - - uint16_t port; - uint8_t status, timer_ctrl; - uint16_t timer_count[2], - timer_cur_count[2]; - - pc_timer_t timers[2]; - - int pos; - int32_t buffer[SOUNDBUFLEN * 2]; -} opl_t; - -extern void opl_set_do_cycles(opl_t *dev, int8_t do_cycles); - -extern uint8_t opl2_read(uint16_t port, void *); -extern void opl2_write(uint16_t port, uint8_t val, void *); -extern void opl2_init(opl_t *); -extern void opl2_update(opl_t *); - -extern uint8_t opl3_read(uint16_t port, void *); -extern void opl3_write(uint16_t port, uint8_t val, void *); -extern void opl3_init(opl_t *); -extern void opl3_update(opl_t *); #endif /*SOUND_OPL_H*/ diff --git a/src/include/86box/snd_opl_nuked.h b/src/include/86box/snd_opl_nuked.h index af65d266b..93ea4ba35 100644 --- a/src/include/86box/snd_opl_nuked.h +++ b/src/include/86box/snd_opl_nuked.h @@ -20,15 +20,5 @@ #ifndef SOUND_OPL_NUKED_H #define SOUND_OPL_NUKED_H -extern void *nuked_init(uint32_t sample_rate); -extern void nuked_close(void *); - -extern uint16_t nuked_write_addr(void *, uint16_t port, uint8_t val); -extern void nuked_write_reg(void *, uint16_t reg, uint8_t v); -extern void nuked_write_reg_buffered(void *, uint16_t reg, uint8_t v); - -extern void nuked_generate(void *, int32_t *buf); -extern void nuked_generate_resampled(void *, int32_t *buf); -extern void nuked_generate_stream(void *, int32_t *sndptr, uint32_t num); #endif /*SOUND_OPL_NUKED_H*/ diff --git a/src/include/86box/snd_sb.h b/src/include/86box/snd_sb.h index bf0b437b0..bf44d4d6d 100644 --- a/src/include/86box/snd_sb.h +++ b/src/include/86box/snd_sb.h @@ -129,7 +129,7 @@ typedef struct sb_t { opl_enabled, mixer_enabled; cms_t cms; - opl_t opl, + fm_drv_t opl, opl2; sb_dsp_t dsp; union { diff --git a/src/include/86box/sound.h b/src/include/86box/sound.h index 2696954ef..71d4942d0 100644 --- a/src/include/86box/sound.h +++ b/src/include/86box/sound.h @@ -120,6 +120,7 @@ extern const device_t sb_16_device; extern const device_t sb_16_pnp_device; extern const device_t sb_16_compat_device; extern const device_t sb_16_compat_nompu_device; +extern const device_t sb_16_reply_mca_device; extern const device_t sb_32_pnp_device; extern const device_t sb_awe32_device; extern const device_t sb_awe32_pnp_device; diff --git a/src/include/86box/timer.h b/src/include/86box/timer.h index 63ca8965b..afbcec140 100644 --- a/src/include/86box/timer.h +++ b/src/include/86box/timer.h @@ -56,6 +56,10 @@ typedef struct pc_timer_t struct pc_timer_t *prev, *next; } pc_timer_t; +#ifdef __cplusplus +extern "C" { +#endif + /*Timestamp of nearest enabled timer. CPU emulation must call timer_process() when TSC matches or exceeds this.*/ extern uint32_t timer_target; @@ -237,4 +241,8 @@ timer_process_inline(void) timer_target = timer_head->ts.ts32.integer; } +#ifdef __cplusplus +} +#endif + #endif /*_TIMER_H_*/ diff --git a/src/machine/m_amstrad.c b/src/machine/m_amstrad.c index 43a8093c8..3b0235d51 100644 --- a/src/machine/m_amstrad.c +++ b/src/machine/m_amstrad.c @@ -2059,7 +2059,7 @@ kbd_write(uint16_t port, uint8_t val, void *priv) speaker_enable = val & 0x02; if (speaker_enable) was_speaker_enable = 1; - pit_ctr_set_gate(&pit->counters[2], val & 0x01); + pit_devs[0].set_gate(pit_devs[0].data, 2, val & 0x01); if (val & 0x80) { /* Keyboard enabled, so enable PA reading. */ diff --git a/src/machine/m_at.c b/src/machine/m_at.c index dc47b6207..df3c4bd8b 100644 --- a/src/machine/m_at.c +++ b/src/machine/m_at.c @@ -66,7 +66,7 @@ machine_at_common_init_ex(const machine_t *model, int type) machine_common_init(model); refresh_at_enable = 1; - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_at); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_at); pic2_init(); dma16_init(); diff --git a/src/machine/m_at_slot1.c b/src/machine/m_at_slot1.c index 188364e1c..f3b225ae1 100644 --- a/src/machine/m_at_slot1.c +++ b/src/machine/m_at_slot1.c @@ -717,7 +717,6 @@ machine_at_m729_init(const machine_t *model) pci_register_slot(0x10, PCI_CARD_NORMAL, 3, 4, 1, 2); device_add(&ali1621_device); device_add(&ali1543c_device); /* +0 */ - device_add(&keyboard_ps2_ami_pci_device); device_add(&sst_flash_29ee010_device); spd_register(SPD_TYPE_SDRAM, 0x7, 512); diff --git a/src/machine/m_at_socket7.c b/src/machine/m_at_socket7.c index 25fa8483e..2856a599c 100644 --- a/src/machine/m_at_socket7.c +++ b/src/machine/m_at_socket7.c @@ -1005,7 +1005,6 @@ machine_at_m560_init(const machine_t *model) pci_register_slot(0x06, PCI_CARD_NORMAL, 4, 1, 2, 3); device_add(&ali1531_device); device_add(&ali1543_device); /* -5 */ - device_add(&keyboard_ps2_ami_pci_device); device_add(&sst_flash_29ee010_device); spd_register(SPD_TYPE_SDRAM, 0x3, 256); @@ -1040,7 +1039,6 @@ machine_at_ms5164_init(const machine_t *model) device_add(&ali1531_device); device_add(&ali1543_device); /* -5 */ - device_add(&keyboard_ps2_ami_pci_device); device_add(&sst_flash_29ee010_device); spd_register(SPD_TYPE_SDRAM, 0x3, 256); diff --git a/src/machine/m_at_sockets7.c b/src/machine/m_at_sockets7.c index c8eeccea0..a32bf3883 100644 --- a/src/machine/m_at_sockets7.c +++ b/src/machine/m_at_sockets7.c @@ -73,7 +73,6 @@ machine_at_p5a_init(const machine_t *model) pci_register_slot(0x06, PCI_CARD_NORMAL, 3, 4, 1, 2); device_add(&ali1541_device); device_add(&ali1543c_device); /* +0 */ - device_add(&keyboard_ps2_ami_pci_device); device_add(&sst_flash_39sf020_device); spd_register(SPD_TYPE_SDRAM, 0x7, 512); device_add(&w83781d_p5a_device); /* fans: Chassis, CPU, Power; temperatures: MB, unused, CPU */ @@ -107,7 +106,6 @@ machine_at_m579_init(const machine_t *model) pci_register_slot(0x14, PCI_CARD_NORMAL, 1, 2, 3, 4); device_add(&ali1541_device); device_add(&ali1543c_device); /* +0 */ - device_add(&keyboard_ps2_ami_pci_device); device_add(&sst_flash_29ee010_device); spd_register(SPD_TYPE_SDRAM, 0x7, 512); @@ -140,7 +138,6 @@ machine_at_5aa_init(const machine_t *model) pci_register_slot(0x0A, PCI_CARD_NORMAL, 3, 4, 1, 2); device_add(&ali1541_device); device_add(&ali1543c_device); /* +0 */ - device_add(&keyboard_ps2_ami_pci_device); device_add(&sst_flash_29ee010_device); spd_register(SPD_TYPE_SDRAM, 0x7, 512); @@ -175,7 +172,6 @@ machine_at_5ax_init(const machine_t *model) pci_register_slot(0x0C, PCI_CARD_NORMAL, 1, 2, 3, 4); device_add(&ali1541_device); device_add(&ali1543c_device); /* +0 */ - device_add(&keyboard_ps2_ami_pci_device); device_add(&sst_flash_29ee010_device); spd_register(SPD_TYPE_SDRAM, 0x7, 512); diff --git a/src/machine/m_europc.c b/src/machine/m_europc.c index 2c035ded1..905515225 100644 --- a/src/machine/m_europc.c +++ b/src/machine/m_europc.c @@ -718,7 +718,7 @@ machine_europc_init(const machine_t *model) return ret; machine_common_init(model); - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_xt); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt); nmi_init(); diff --git a/src/machine/m_pcjr.c b/src/machine/m_pcjr.c index 2491e5064..2665ea164 100644 --- a/src/machine/m_pcjr.c +++ b/src/machine/m_pcjr.c @@ -627,7 +627,7 @@ kbd_write(uint16_t port, uint8_t val, void *priv) speaker_enable = val & 2; if (speaker_enable) was_speaker_enable = 1; - pit_ctr_set_gate(&pit->counters[2], val & 1); + pit_devs[0].set_gate(pit_devs[0].data, 2, val & 1); sn76489_mute = speaker_mute = 1; switch (val & 0x60) { case 0x00: @@ -642,7 +642,7 @@ kbd_write(uint16_t port, uint8_t val, void *priv) case 0xa0: nmi_mask = val & 0x80; - pit_ctr_set_using_timer(&pit->counters[1], !(val & 0x20)); + pit_devs[0].set_using_timer(pit_devs[0].data, 1, !(val & 0x20)); break; } } @@ -770,6 +770,18 @@ speed_changed(void *priv) recalc_timings(pcjr); } +void +pit_irq0_timer_pcjr(int new_out, int old_out) +{ + if (new_out && !old_out) { + picint(1); + pit_devs[0].ctr_clock(pit_devs[0].data, 1); + } + + if (!new_out) + picintc(1); +} + static const device_config_t pcjr_config[] = { { .name = "display_type", diff --git a/src/machine/m_ps1.c b/src/machine/m_ps1.c index 101746a03..19abb00bf 100644 --- a/src/machine/m_ps1.c +++ b/src/machine/m_ps1.c @@ -327,7 +327,7 @@ ps1_common_init(const machine_t *model) machine_common_init(model); refresh_at_enable = 1; - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_at); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_at); dma16_init(); pic2_init(); diff --git a/src/machine/m_ps2_isa.c b/src/machine/m_ps2_isa.c index 24fa0dfb1..094fc8a5f 100644 --- a/src/machine/m_ps2_isa.c +++ b/src/machine/m_ps2_isa.c @@ -190,7 +190,7 @@ ps2_isa_common_init(const machine_t *model) machine_common_init(model); refresh_at_enable = 1; - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_at); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_at); dma16_init(); pic2_init(); diff --git a/src/machine/m_ps2_mca.c b/src/machine/m_ps2_mca.c index 7aea5a319..665d42972 100644 --- a/src/machine/m_ps2_mca.c +++ b/src/machine/m_ps2_mca.c @@ -1363,7 +1363,8 @@ machine_ps2_common_init(const machine_t *model) device_add(&ps_no_nmi_nvr_device); pic2_init(); - pit_ps2_init(); + int pit_type = ((pit_mode == -1 && is486) || pit_mode == 1) ? PIT_8254_FAST : PIT_8254; + pit_ps2_init(pit_type); nmi_mask = 0x80; diff --git a/src/machine/m_xt.c b/src/machine/m_xt.c index f95ee2fe0..5b31920c5 100644 --- a/src/machine/m_xt.c +++ b/src/machine/m_xt.c @@ -24,7 +24,7 @@ machine_xt_common_init(const machine_t *model) { machine_common_init(model); - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_xt); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt); if (fdc_type == FDC_INTERNAL) device_add(&fdc_xt_device); diff --git a/src/machine/m_xt_compaq.c b/src/machine/m_xt_compaq.c index bd355a161..a705f0e2b 100644 --- a/src/machine/m_xt_compaq.c +++ b/src/machine/m_xt_compaq.c @@ -50,7 +50,7 @@ machine_xt_compaq_deskpro_init(const machine_t *model) machine_common_init(model); - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_xt); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt); device_add(&keyboard_xt_compaq_device); if (fdc_type == FDC_INTERNAL) @@ -78,7 +78,7 @@ machine_xt_compaq_portable_init(const machine_t *model) machine_common_init(model); - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_xt); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt); device_add(&keyboard_xt_compaq_device); if (fdc_type == FDC_INTERNAL) diff --git a/src/machine/m_xt_laserxt.c b/src/machine/m_xt_laserxt.c index 58b7d3774..21681a5c2 100644 --- a/src/machine/m_xt_laserxt.c +++ b/src/machine/m_xt_laserxt.c @@ -167,7 +167,7 @@ machine_xt_lxt3_init(const machine_t *model) machine_common_init(model); - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_xt); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt); device_add(&keyboard_xt_lxt3_device); diff --git a/src/machine/m_xt_olivetti.c b/src/machine/m_xt_olivetti.c index 716bb9c18..62fcda138 100644 --- a/src/machine/m_xt_olivetti.c +++ b/src/machine/m_xt_olivetti.c @@ -241,7 +241,7 @@ m24_kbd_write(uint16_t port, uint8_t val, void *priv) speaker_enable = val & 2; if (speaker_enable) was_speaker_enable = 1; - pit_ctr_set_gate(&pit->counters[2], val & 1); + pit_devs[0].set_gate(pit_devs[0].data, 2, val & 1); break; } } @@ -792,7 +792,7 @@ machine_xt_m240_init(const machine_t *model) machine_common_init(model); - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_xt); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt); /* Address 66-67 = mainboard dip-switch settings */ io_sethandler(0x0066, 2, m24_read, NULL, NULL, NULL, NULL, NULL, NULL); @@ -846,7 +846,7 @@ machine_xt_m19_init(const machine_t *model) machine_common_init(model); - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_xt); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt); /* On-board FDC cannot be disabled */ device_add(&fdc_xt_device); diff --git a/src/machine/m_xt_philips.c b/src/machine/m_xt_philips.c index cafccf061..b10e3a37e 100644 --- a/src/machine/m_xt_philips.c +++ b/src/machine/m_xt_philips.c @@ -152,7 +152,7 @@ machine_xt_philips_common_init(const machine_t *model) { machine_common_init(model); - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_xt); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt); nmi_init(); diff --git a/src/machine/m_xt_t1000.c b/src/machine/m_xt_t1000.c index 961cc627a..ba96b74e7 100644 --- a/src/machine/m_xt_t1000.c +++ b/src/machine/m_xt_t1000.c @@ -911,7 +911,7 @@ machine_xt_t1000_init(const machine_t *model) machine_common_init(model); - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_xt); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt); device_add(&keyboard_xt_device); t1000.fdc = device_add(&fdc_xt_device); nmi_init(); @@ -979,7 +979,7 @@ machine_xt_t1200_init(const machine_t *model) write_t1200_nvram, NULL, NULL, NULL, MEM_MAPPING_EXTERNAL, &t1000); - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_xt); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt); device_add(&keyboard_xt_device); t1000.fdc = device_add(&fdc_xt_t1x00_device); nmi_init(); diff --git a/src/machine/m_xt_zenith.c b/src/machine/m_xt_zenith.c index f9806b610..6eab9aee2 100644 --- a/src/machine/m_xt_zenith.c +++ b/src/machine/m_xt_zenith.c @@ -122,7 +122,7 @@ machine_zenith_init(const machine_t *model){ device_add(&zenith_scratchpad_device); - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_xt); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt); device_add(&keyboard_xt_zenith_device); diff --git a/src/machine/machine.c b/src/machine/machine.c index 67819b247..774b972c2 100644 --- a/src/machine/machine.c +++ b/src/machine/machine.c @@ -161,6 +161,16 @@ machine_available(int m) } +void +pit_irq0_timer(int new_out, int old_out) +{ + if (new_out && !old_out) + picint(1); + + if (!new_out) + picintc(1); +} + void machine_common_init(const machine_t *model) { @@ -168,5 +178,10 @@ machine_common_init(const machine_t *model) pic_init(); dma_init(); - pit_common_init(!!IS_AT(machine), pit_irq0_timer, NULL); + int pit_type = IS_AT(machine) ? PIT_8254 : PIT_8253; + /* Select fast PIT if needed */ + if ((pit_mode == -1 && is486) || pit_mode == 1) + pit_type += 2; + + pit_common_init(pit_type, pit_irq0_timer, NULL); } diff --git a/src/machine/machine_table.c b/src/machine/machine_table.c index 6810c2510..ac04fa413 100644 --- a/src/machine/machine_table.c +++ b/src/machine/machine_table.c @@ -4723,7 +4723,7 @@ const machine_t machines[] = { .max_multi = 0 }, .bus_flags = MACHINE_PS2, - .flags = MACHINE_IDE_DUAL | MACHINE_VIDEO, + .flags = MACHINE_IDE_DUAL, /* No MACHINE_VIDEO yet, because on-board video is not yet implemented. */ .ram = { .min = 1024, .max = 32768, @@ -4758,7 +4758,7 @@ const machine_t machines[] = { .max_multi = 0 }, .bus_flags = MACHINE_PS2, - .flags = MACHINE_IDE_DUAL | MACHINE_VIDEO, + .flags = MACHINE_IDE_DUAL, .ram = { .min = 1024, .max = 32768, diff --git a/src/mem/rom.c b/src/mem/rom.c index eb2b5791b..debdf5c39 100644 --- a/src/mem/rom.c +++ b/src/mem/rom.c @@ -646,7 +646,7 @@ rom_init_oddeven(rom_t *rom, char *fn, uint32_t addr, int sz, int mask, int off, addr, sz, rom_read, rom_readw, rom_readl, NULL, NULL, NULL, - rom->rom, flags | MEM_MAPPING_ROM, rom); + rom->rom, flags | MEM_MAPPING_ROM_WS, rom); return(0); } @@ -674,7 +674,7 @@ rom_init_interleaved(rom_t *rom, char *fnl, char *fnh, uint32_t addr, int sz, in addr, sz, rom_read, rom_readw, rom_readl, NULL, NULL, NULL, - rom->rom, flags | MEM_MAPPING_ROM, rom); + rom->rom, flags | MEM_MAPPING_ROM_WS, rom); return(0); } diff --git a/src/network/net_wd8003.c b/src/network/net_wd8003.c index 81e6d91ec..81429fe19 100644 --- a/src/network/net_wd8003.c +++ b/src/network/net_wd8003.c @@ -540,7 +540,6 @@ wd_mca_read(int port, void *priv) #define MCA_6FC0_IRQS { 3, 4, 10, 15 } - static void wd_mca_write(int port, uint8_t val, void *priv) { @@ -582,6 +581,68 @@ wd_mca_write(int port, uint8_t val, void *priv) dev->base_address, dev->irq, dev->ram_addr); } +static void +wd_8013epa_mca_write(int port, uint8_t val, void *priv) +{ + wd_t *dev = (wd_t *)priv; + + /* MCA does not write registers below 0x0100. */ + if (port < 0x0102) return; + + /* Save the MCA register value. */ + dev->pos_regs[port & 7] = val; + + /* + * The PS/2 Model 80 BIOS always enables a card if it finds one, + * even if no resources were assigned yet (because we only added + * the card, but have not run AutoConfig yet...) + * + * So, remove current address, if any. + */ + if (dev->base_address) + wd_io_remove(dev, dev->base_address); + + dev->base_address = 0x800 + ((dev->pos_regs[2] & 0xf0) << 8); + + switch (dev->pos_regs[5] & 0x0c) { + case 0: + dev->irq = 3; + break; + case 4: + dev->irq = 4; + break; + case 8: + dev->irq = 10; + break; + case 0x0c: + dev->irq = 14; + break; + } + + if (dev->pos_regs[3] & 0x10) + dev->ram_size = 0x4000; + else + dev->ram_size = 0x2000; + + dev->ram_addr = ((dev->pos_regs[3] & 0x0f) << 13) + 0xc0000; + if (dev->pos_regs[3] & 0x80) + dev->ram_addr += 0xf00000; + + /* Initialize the device if fully configured. */ + /* Register (new) I/O handler. */ + if (dev->pos_regs[2] & 0x01) + wd_io_set(dev, dev->base_address); + + mem_mapping_set_addr(&dev->ram_mapping, dev->ram_addr, dev->ram_size); + + mem_mapping_disable(&dev->ram_mapping); + if ((dev->msr & WE_MSR_ENABLE_RAM) && (dev->pos_regs[2] & 0x01)) + mem_mapping_enable(&dev->ram_mapping); + + wdlog("%s: attached IO=0x%X IRQ=%d, RAM addr=0x%06x\n", dev->name, + dev->base_address, dev->irq, dev->ram_addr); +} + static uint8_t wd_mca_feedb(void *priv) @@ -624,9 +685,12 @@ wd_init(const device_t *info) dev->maclocal[5] = (mac & 0xff); } - if ((dev->board == WD8003ETA) || (dev->board == WD8003EA)) - mca_add(wd_mca_read, wd_mca_write, wd_mca_feedb, NULL, dev); - else { + if ((dev->board == WD8003ETA) || (dev->board == WD8003EA) || dev->board == WD8013EPA) { + if (dev->board == WD8013EPA) + mca_add(wd_mca_read, wd_8013epa_mca_write, wd_mca_feedb, NULL, dev); + else + mca_add(wd_mca_read, wd_mca_write, wd_mca_feedb, NULL, dev); + } else { dev->base_address = device_get_config_hex16("base"); dev->irq = device_get_config_int("irq"); dev->ram_addr = device_get_config_hex20("ram_addr"); @@ -679,12 +743,20 @@ wd_init(const device_t *info) dev->board_chip = WE_ID_SOFT_CONFIG; /* Ethernet, MCA, no interface chip, RAM 16k */ case WD8003ETA: - dev->board_chip |= 0x05 | WE_ID_BUS_MCA; + dev->board_chip |= WE_TYPE_WD8013EBT | WE_ID_BUS_MCA; dev->ram_size = 0x4000; dev->pos_regs[0] = 0xC0; dev->pos_regs[1] = 0x6F; dev->bit16 = 3; break; + + case WD8013EPA: + dev->board_chip = WE_TYPE_WD8013EP | WE_ID_BUS_MCA; + dev->ram_size = device_get_config_int("ram_size"); + dev->pos_regs[0] = 0xC8; + dev->pos_regs[1] = 0x61; + dev->bit16 = 3; + break; } dev->irr |= WE_IRR_ENABLE_IRQ; @@ -969,6 +1041,31 @@ static const device_config_t wd8013_config[] = { { .name = "", .description = "", .type = CONFIG_END } }; +static const device_config_t wd8013epa_config[] = { + { + .name = "ram_size", + .description = "Initial RAM size", + .type = CONFIG_SELECTION, + .default_string = "", + .default_int = 16384, + .file_filter = "", + .spinner = { 0 }, + .selection = { + { .description = "8 kB", .value = 8192 }, + { .description = "16 kB", .value = 16384 }, + { .description = "" } + }, + }, + { + .name = "mac", + .description = "MAC Address", + .type = CONFIG_MAC, + .default_string = "", + .default_int = -1 + }, + { .name = "", .description = "", .type = CONFIG_END } +}; + static const device_config_t mca_mac_config[] = { { .name = "mac", @@ -1050,3 +1147,17 @@ const device_t wd8003ea_device = { .force_redraw = NULL, .config = mca_mac_config }; + +const device_t wd8013epa_device = { + .name = "Western Digital WD8013EP/A", + .internal_name = "wd8013epa", + .flags = DEVICE_MCA, + .local = WD8013EPA, + .init = wd_init, + .close = wd_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = wd8013epa_config +}; diff --git a/src/network/network.c b/src/network/network.c index 49915024a..6a7fbdfa5 100644 --- a/src/network/network.c +++ b/src/network/network.c @@ -103,6 +103,7 @@ static netcard_t net_cards[] = { { ðernext_mc_device, NULL }, { &wd8003eta_device, NULL }, { &wd8003ea_device, NULL }, + { &wd8013epa_device, NULL }, { &pcnet_am79c973_device, NULL }, { &pcnet_am79c970a_device, NULL }, { &rtl8029as_device, NULL }, diff --git a/src/pic.c b/src/pic.c index f99b31901..306b3e049 100644 --- a/src/pic.c +++ b/src/pic.c @@ -755,8 +755,8 @@ picinterrupt() pic.interrupt |= 0x40; /* Mark slave pending. */ } - if ((pic.interrupt == 0) && (pit2 != NULL)) - pit_ctr_set_gate(&pit2->counters[0], 0); + if ((pic.interrupt == 0) && (pit_devs[1].data != NULL)) + pit_devs[1].set_gate(pit_devs[1].data, 0, 0); /* Two ACK's - do them in a loop to avoid potential compiler misoptimizations. */ for (i = 0; i < 2; i++) { diff --git a/src/pit.c b/src/pit.c index f19a1acf7..ba71928ca 100644 --- a/src/pit.c +++ b/src/pit.c @@ -34,14 +34,15 @@ #include <86box/pic.h> #include <86box/timer.h> #include <86box/pit.h> +#include <86box/pit_fast.h> #include <86box/ppi.h> #include <86box/machine.h> #include <86box/sound.h> #include <86box/snd_speaker.h> #include <86box/video.h> +pit_intf_t pit_devs[2]; -pit_t *pit, *pit2; double cpuclock, PITCONSTD, SYSCLK, isa_timing, @@ -67,12 +68,6 @@ int64_t firsttime = 1; #define PIT_SECONDARY 128 /* The PIT is secondary (ports 0048-004B). */ -enum { - PIT_8253 = 0, - PIT_8254 -}; - - #ifdef ENABLE_PIT_LOG int pit_do_log = ENABLE_PIT_LOG; @@ -293,8 +288,11 @@ ctr_tick(ctr_t *ctr) static void -ctr_clock(ctr_t *ctr) +ctr_clock(void *data, int counter_id) { + pit_t *pit = (pit_t *)data; + ctr_t *ctr = &pit->counters[counter_id]; + /* FIXME: Is this even needed? */ if ((ctr->state == 3) && (ctr->m != 2) && (ctr->m != 3)) return; @@ -379,35 +377,47 @@ ctr_latch_count(ctr_t *ctr) uint16_t -pit_ctr_get_count(ctr_t *ctr) +pit_ctr_get_count(void *data, int counter_id) { + pit_t *pit = (pit_t *)data; + ctr_t *ctr = &pit->counters[counter_id]; + return (uint16_t) ctr->l; } void -pit_ctr_set_load_func(ctr_t *ctr, void (*func)(uint8_t new_m, int new_count)) +pit_ctr_set_load_func(void *data, int counter_id, void (*func)(uint8_t new_m, int new_count)) { - if (ctr == NULL) + if (data == NULL) return; + pit_t *pit = (pit_t *)data; + ctr_t *ctr = &pit->counters[counter_id]; + ctr->load_func = func; } void -pit_ctr_set_out_func(ctr_t *ctr, void (*func)(int new_out, int old_out)) +pit_ctr_set_out_func(void *data, int counter_id, void (*func)(int new_out, int old_out)) { - if (ctr == NULL) + if (data == NULL) return; + pit_t *pit = (pit_t *)data; + ctr_t *ctr = &pit->counters[counter_id]; + ctr->out_func = func; } void -pit_ctr_set_gate(ctr_t *ctr, int gate) +pit_ctr_set_gate(void *data, int counter_id, int gate) { + pit_t *pit = (pit_t *)data; + ctr_t *ctr = &pit->counters[counter_id]; + int old = ctr->gate; uint8_t mode = ctr->m & 3; @@ -470,10 +480,12 @@ pit_ctr_set_clock(ctr_t *ctr, int clock) void -pit_ctr_set_using_timer(ctr_t *ctr, int using_timer) +pit_ctr_set_using_timer(void *data, int counter_id, int using_timer) { - timer_process(); - + if (tsc > 0) + timer_process(); + pit_t *pit = (pit_t *)data; + ctr_t *ctr = &pit->counters[counter_id]; ctr->using_timer = using_timer; } @@ -673,46 +685,19 @@ pit_read(uint16_t addr, void *priv) } -/* FIXME: Should be moved to machine.c (default for most machine). */ -void -pit_irq0_timer(int new_out, int old_out) -{ - if (new_out && !old_out) - picint(1); - - if (!new_out) - picintc(1); -} - - -void -pit_irq0_timer_pcjr(int new_out, int old_out) -{ - if (new_out && !old_out) { - picint(1); - ctr_clock(&pit->counters[1]); - } - - if (!new_out) - picintc(1); -} - - void pit_irq0_timer_ps2(int new_out, int old_out) { - ctr_t *ctr = &pit2->counters[0]; - if (new_out && !old_out) { picint(1); - pit_ctr_set_gate(ctr, 1); + pit_devs[1].set_gate(pit_devs[1].data, 0, 1); } if (!new_out) picintc(1); if (!new_out && old_out) - ctr_clock(ctr); + pit_devs[1].ctr_clock(pit_devs[1].data, 0); } @@ -742,7 +727,8 @@ pit_speaker_timer(int new_out, int old_out) speaker_update(); - l = pit->counters[2].l ? pit->counters[2].l : 0x10000; + uint16_t count = pit_devs[0].get_count(pit_devs[0].data, 2); + l = count ? count : 0x10000; if (l < 25) speakon = 0; else @@ -809,11 +795,11 @@ pit_close(void *priv) { pit_t *dev = (pit_t *) priv; - if (dev == pit) - pit = NULL; + if (dev == pit_devs[0].data) + pit_devs[0].data = NULL; - if (dev == pit2) - pit2 = NULL; + if (dev == pit_devs[1].data) + pit_devs[1].data = NULL; if (dev != NULL) free(dev); @@ -915,47 +901,83 @@ pit_t * pit_common_init(int type, void (*out0)(int new_out, int old_out), void (*out1)(int new_out, int old_out)) { int i; + void *pit; + + pit_intf_t *pit_intf = &pit_devs[0]; switch (type) { case PIT_8253: default: pit = device_add(&i8253_device); + *pit_intf = pit_classic_intf; break; case PIT_8254: pit = device_add(&i8254_device); + *pit_intf = pit_classic_intf; break; + case PIT_8253_FAST: + pit = device_add(&i8253_fast_device); + *pit_intf = pit_fast_intf; + break; + case PIT_8254_FAST: + pit = device_add(&i8254_fast_device); + *pit_intf = pit_fast_intf; + break; + } + pit_intf->data = pit; + for (i = 0; i < 3; i++) { - pit->counters[i].gate = 1; - pit->counters[i].using_timer = 1; + pit_intf->set_gate(pit_intf->data, i, 1); + pit_intf->set_using_timer(pit_intf->data, i, 1); } - pit_ctr_set_out_func(&pit->counters[0], out0); - pit_ctr_set_out_func(&pit->counters[1], out1); - pit_ctr_set_out_func(&pit->counters[2], pit_speaker_timer); - pit_ctr_set_load_func(&pit->counters[2], speaker_set_count); - pit->counters[2].gate = 0; + pit_intf->set_out_func(pit_intf->data, 0, out0); + pit_intf->set_out_func(pit_intf->data, 1, out1); + pit_intf->set_out_func(pit_intf->data, 2, pit_speaker_timer); + pit_intf->set_load_func(pit_intf->data, 2, speaker_set_count); + + pit_intf->set_gate(pit_intf->data, 2, 0); return pit; } pit_t * -pit_ps2_init(void) +pit_ps2_init(int type) { - pit2 = device_add(&i8254_ps2_device); + void *pit; - pit_handler(1, 0x0044, 0x0001, pit2); - pit_handler(1, 0x0047, 0x0001, pit2); + pit_intf_t *ps2_pit = &pit_devs[1]; - pit2->counters[0].gate = 0; - pit2->counters[0].using_timer = pit2->counters[1].using_timer = pit2->counters[2].using_timer = 0; + switch (type) { + case PIT_8254: + default: + pit = device_add(&i8254_ps2_device); + *ps2_pit = pit_classic_intf; + break; - pit_ctr_set_out_func(&pit->counters[0], pit_irq0_timer_ps2); - pit_ctr_set_out_func(&pit2->counters[0], pit_nmi_timer_ps2); + case PIT_8254_FAST: + pit = device_add(&i8254_ps2_fast_device); + *ps2_pit = pit_fast_intf; + break; + } - return pit2; + ps2_pit->data = pit; + + ps2_pit->set_gate(ps2_pit->data, 0, 0); + for (int i = 0; i < 3; i++) { + ps2_pit->set_using_timer(ps2_pit->data, i, 0); + } + + io_sethandler(0x0044, 0x0001, ps2_pit->read, NULL, NULL, ps2_pit->write, NULL, NULL, pit); + io_sethandler(0x0047, 0x0001, ps2_pit->read, NULL, NULL, ps2_pit->write, NULL, NULL, pit); + + pit_devs[0].set_out_func(pit_devs[0].data, 0, pit_irq0_timer_ps2); + ps2_pit->set_out_func(ps2_pit->data, 0, pit_nmi_timer_ps2); + + return pit; } @@ -1063,3 +1085,15 @@ pit_set_clock(int clock) device_speed_changed(); } + +const pit_intf_t pit_classic_intf = { + &pit_read, + &pit_write, + &pit_ctr_get_count, + &pit_ctr_set_gate, + &pit_ctr_set_using_timer, + &pit_ctr_set_out_func, + &pit_ctr_set_load_func, + &ctr_clock, + NULL, +}; \ No newline at end of file diff --git a/src/pit_fast.c b/src/pit_fast.c new file mode 100644 index 000000000..704cfd68c --- /dev/null +++ b/src/pit_fast.c @@ -0,0 +1,706 @@ +/* + * 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 Intel 8253/8254 Programmable Interval + * Timer. + * + * + * + * Author: Miran Grca, + * Copyright 2019 Miran Grca. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#define HAVE_STDARG_H +#include <86box/86box.h> +#include "cpu.h" +#include <86box/device.h> +#include <86box/timer.h> +#include <86box/cassette.h> +#include <86box/dma.h> +#include <86box/io.h> +#include <86box/nmi.h> +#include <86box/pic.h> +#include <86box/timer.h> +#include <86box/pit.h> +#include <86box/pit_fast.h> +#include <86box/ppi.h> +#include <86box/machine.h> +#include <86box/sound.h> +#include <86box/snd_speaker.h> +#include <86box/video.h> + +#define PIT_PS2 16 /* The PIT is the PS/2's second PIT. */ +#define PIT_EXT_IO 32 /* The PIT has externally specified port I/O. */ +#define PIT_CUSTOM_CLOCK 64 /* The PIT uses custom clock inputs provided by another provider. */ +#define PIT_SECONDARY 128 /* The PIT is secondary (ports 0048-004B). */ + +#ifdef ENABLE_PIT_LOG +int pit_do_log = ENABLE_PIT_LOG; + +static void +pit_log(const char *fmt, ...) +{ + va_list ap; + + if (pit_do_log) { + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); + } +} +#else +#define pit_log(fmt, ...) +#endif + +static void +pitf_ctr_set_out(ctrf_t *ctr, int out) +{ + if (ctr == NULL) + return; + + if (ctr->out_func != NULL) + ctr->out_func(out, ctr->out); + ctr->out = out; +} + +static void +pitf_ctr_set_load_func(void *data, int counter_id, void (*func)(uint8_t new_m, int new_count)) +{ + if (data == NULL) + return; + + pitf_t *pit = (pitf_t *)data; + ctrf_t *ctr = &pit->counters[counter_id]; + + ctr->load_func = func; +} + +static uint16_t +pitf_ctr_get_count(void *data, int counter_id) +{ + pitf_t *pit = (pitf_t *)data; + ctrf_t *ctr = &pit->counters[counter_id]; + return (uint16_t) ctr->l; +} + +static void +pitf_ctr_set_out_func(void *data, int counter_id, void (*func)(int new_out, int old_out)) +{ + if (data == NULL) + return; + + pitf_t *pit = (pitf_t *)data; + ctrf_t *ctr = &pit->counters[counter_id]; + + ctr->out_func = func; +} + +static void +pitf_ctr_set_using_timer(void *data, int counter_id, int using_timer) +{ + if (tsc > 0) + timer_process(); + + pitf_t *pit = (pitf_t *)data; + ctrf_t *ctr = &pit->counters[counter_id]; + ctr->using_timer = using_timer; +} + +static int +pitf_read_timer(ctrf_t *ctr) +{ + if (ctr->using_timer && !(ctr->m == 3 && !ctr->gate) && timer_is_enabled(&ctr->timer)) { + int read = (int) ((timer_get_remaining_u64(&ctr->timer)) / PITCONST); + if (ctr->m == 2) + read++; + if (read < 0) + read = 0; + if (read > 0x10000) + read = 0x10000; + if (ctr->m == 3) + read <<= 1; + return read; + } + if (ctr->m == 2) + return ctr->count + 1; + return ctr->count; +} + +/*Dump timer count back to pit->count[], and disable timer. This should be used + when stopping a PIT timer, to ensure the correct value can be read back.*/ +static void +pitf_dump_and_disable_timer(ctrf_t *ctr) +{ + if (ctr->using_timer && timer_is_enabled(&ctr->timer)) { + ctr->count = pitf_read_timer(ctr); + timer_disable(&ctr->timer); + } +} + +static void +pitf_ctr_load(ctrf_t *ctr) +{ + int l = ctr->l ? ctr->l : 0x10000; + + ctr->newcount = 0; + ctr->disabled = 0; + + switch (ctr->m) { + case 0: /*Interrupt on terminal count*/ + ctr->count = l; + if (ctr->using_timer) + timer_set_delay_u64(&ctr->timer, (uint64_t) (l * PITCONST)); + pitf_ctr_set_out(ctr, 0); + ctr->thit = 0; + ctr->enabled = ctr->gate; + break; + case 1: /*Hardware retriggerable one-shot*/ + ctr->enabled = 1; + break; + case 2: /*Rate generator*/ + if (ctr->initial) { + ctr->count = l - 1; + if (ctr->using_timer) + timer_set_delay_u64(&ctr->timer, (uint64_t) ((l - 1) * PITCONST)); + pitf_ctr_set_out(ctr, 1); + ctr->thit = 0; + } + ctr->enabled = ctr->gate; + break; + case 3: /*Square wave mode*/ + if (ctr->initial) { + ctr->count = l; + if (ctr->using_timer) + timer_set_delay_u64(&ctr->timer, (uint64_t) (((l + 1) >> 1) * PITCONST)); + pitf_ctr_set_out(ctr, 1); + ctr->thit = 0; + } + ctr->enabled = ctr->gate; + break; + case 4: /*Software triggered stobe*/ + if (!ctr->thit && !ctr->initial) + ctr->newcount = 1; + else { + ctr->count = l; + if (ctr->using_timer) + timer_set_delay_u64(&ctr->timer, (uint64_t) (l * PITCONST)); + pitf_ctr_set_out(ctr, 0); + ctr->thit = 0; + } + ctr->enabled = ctr->gate; + break; + case 5: /*Hardware triggered stobe*/ + ctr->enabled = 1; + break; + } + + if (ctr->load_func != NULL) + ctr->load_func(ctr->m, l); + + ctr->initial = 0; + ctr->running = ctr->enabled && ctr->using_timer && !ctr->disabled; + if (ctr->using_timer && !ctr->running) + pitf_dump_and_disable_timer(ctr); +} + +static void +pitf_set_gate_no_timer(ctrf_t *ctr, int gate) +{ + int l = ctr->l ? ctr->l : 0x10000; + + if (ctr->disabled) { + ctr->gate = gate; + return; + } + + switch (ctr->m) { + case 0: /*Interrupt on terminal count*/ + case 4: /*Software triggered stobe*/ + if (ctr->using_timer && !ctr->running) + timer_set_delay_u64(&ctr->timer, (uint64_t) (l * PITCONST)); + ctr->enabled = gate; + break; + case 1: /*Hardware retriggerable one-shot*/ + case 5: /*Hardware triggered stobe*/ + if (gate && !ctr->gate) { + ctr->count = l; + if (ctr->using_timer) + timer_set_delay_u64(&ctr->timer, (uint64_t) (l * PITCONST)); + pitf_ctr_set_out(ctr, 0); + ctr->thit = 0; + ctr->enabled = 1; + } + break; + case 2: /*Rate generator*/ + if (gate && !ctr->gate) { + ctr->count = l - 1; + if (ctr->using_timer) + timer_set_delay_u64(&ctr->timer, (uint64_t) (l * PITCONST)); + pitf_ctr_set_out(ctr, 1); + ctr->thit = 0; + } + ctr->enabled = gate; + break; + case 3: /*Square wave mode*/ + if (gate && !ctr->gate) { + ctr->count = l; + if (ctr->using_timer) + timer_set_delay_u64(&ctr->timer, (uint64_t) (((l + 1) >> 1) * PITCONST)); + pitf_ctr_set_out(ctr, 1); + ctr->thit = 0; + } + ctr->enabled = gate; + break; + } + ctr->gate = gate; + ctr->running = ctr->enabled && ctr->using_timer && !ctr->disabled; + if (ctr->using_timer && !ctr->running) + pitf_dump_and_disable_timer(ctr); +} + +static void +pitf_ctr_set_gate(void *data, int counter_id, int gate) +{ + pitf_t *pit = (pitf_t *)data; + ctrf_t *ctr = &pit->counters[counter_id]; + + if (ctr->disabled) { + ctr->gate = gate; + return; + } + + pitf_set_gate_no_timer(ctr, gate); +} + +static void +pitf_over(ctrf_t *ctr) +{ + int l = ctr->l ? ctr->l : 0x10000; + if (ctr->disabled) { + ctr->count += 0xffff; + if (ctr->using_timer) + timer_advance_u64(&ctr->timer, (uint64_t) (0xffff * PITCONST)); + return; + } + + switch (ctr->m) { + case 0: /*Interrupt on terminal count*/ + case 1: /*Hardware retriggerable one-shot*/ + if (!ctr->thit) + pitf_ctr_set_out(ctr, 1); + ctr->thit = 1; + ctr->count += 0xffff; + if (ctr->using_timer) + timer_advance_u64(&ctr->timer, (uint64_t) (0xffff * PITCONST)); + break; + case 2: /*Rate generator*/ + ctr->count += l; + if (ctr->using_timer) + timer_advance_u64(&ctr->timer, (uint64_t) (l * PITCONST)); + pitf_ctr_set_out(ctr, 0); + pitf_ctr_set_out(ctr, 1); + break; + case 3: /*Square wave mode*/ + if (ctr->out) { + pitf_ctr_set_out(ctr, 0); + ctr->count += (l >> 1); + if (ctr->using_timer) + timer_advance_u64(&ctr->timer, (uint64_t) ((l >> 1) * PITCONST)); + } else { + pitf_ctr_set_out(ctr, 1); + ctr->count += ((l + 1) >> 1); + if (ctr->using_timer) + timer_advance_u64(&ctr->timer, (uint64_t) (((l + 1) >> 1) * PITCONST)); + } + // if (!t) pclog("pit_over: square wave mode c=%x %lli %f\n", pit.c[t], tsc, PITCONST); + break; + case 4: /*Software triggered strove*/ + if (!ctr->thit) { + pitf_ctr_set_out(ctr, 0); + pitf_ctr_set_out(ctr, 1); + } + if (ctr->newcount) { + ctr->newcount = 0; + ctr->count += l; + if (ctr->using_timer) + timer_advance_u64(&ctr->timer, (uint64_t) (l * PITCONST)); + } else { + ctr->thit = 1; + ctr->count += 0xffff; + if (ctr->using_timer) + timer_advance_u64(&ctr->timer, (uint64_t) (0xffff * PITCONST)); + } + break; + case 5: /*Hardware triggered strove*/ + if (!ctr->thit) { + pitf_ctr_set_out(ctr, 0); + pitf_ctr_set_out(ctr, 1); + } + ctr->thit = 1; + ctr->count += 0xffff; + if (ctr->using_timer) + timer_advance_u64(&ctr->timer, (uint64_t) (0xffff * PITCONST)); + break; + } + ctr->running = ctr->enabled && ctr->using_timer && !ctr->disabled; + if (ctr->using_timer && !ctr->running) + pitf_dump_and_disable_timer(ctr); +} + +static __inline void +pitf_ctr_latch_count(ctrf_t *ctr) +{ + ctr->rl = pitf_read_timer(ctr); + // pclog("Timer latch %f %04X %04X\n",pit->c[0],pit->rl[0],pit->l[0]); + // pit->ctrl |= 0x30; + ctr->rereadlatch = 0; + ctr->rm = 3; + ctr->latched = 1; +} + +static __inline void +pitf_ctr_latch_status(ctrf_t *ctr) +{ + ctr->read_status = (ctr->ctrl & 0x3f) | (ctr->out ? 0x80 : 0); + ctr->do_read_status = 1; +} + +static void +pitf_write(uint16_t addr, uint8_t val, void *priv) +{ + pitf_t *dev = (pitf_t *) priv; + int t = (addr & 3); + ctrf_t *ctr; + + pit_log("[%04X:%08X] pit_write(%04X, %02X, %08X)\n", CS, cpu_state.pc, addr, val, priv); + + switch (addr & 3) { + case 3: /* control */ + t = val >> 6; + + if (t == 3) { + if (dev->flags & PIT_8254) { + /* This is 8254-only. */ + if (!(val & 0x20)) { + if (val & 2) + pitf_ctr_latch_count(&dev->counters[0]); + if (val & 4) + pitf_ctr_latch_count(&dev->counters[1]); + if (val & 8) + pitf_ctr_latch_count(&dev->counters[2]); + pit_log("PIT %i: Initiated readback command\n", t); + } + if (!(val & 0x10)) { + if (val & 2) + pitf_ctr_latch_status(&dev->counters[0]); + if (val & 4) + pitf_ctr_latch_status(&dev->counters[1]); + if (val & 8) + pitf_ctr_latch_status(&dev->counters[2]); + } + } + } else { + dev->ctrl = val; + ctr = &dev->counters[t]; + + if (!(dev->ctrl & 0x30)) { + pitf_ctr_latch_count(ctr); + dev->ctrl |= 0x30; + pit_log("PIT %i: Initiated latched read, %i bytes latched\n", + t, ctr->latched); + } else { + ctr->ctrl = val; + ctr->rm = ctr->wm = (ctr->ctrl >> 4) & 3; + ctr->m = (val >> 1) & 7; + if (ctr->m > 5) + ctr->m &= 3; + if (!(ctr->rm)) { + ctr->rm = 3; + ctr->rl = pitf_read_timer(ctr); + } + ctr->rereadlatch = 1; + ctr->initial = 1; + if (!ctr->m) + pitf_ctr_set_out(ctr, 0); + else + pitf_ctr_set_out(ctr, 1); + ctr->disabled = 1; + + pit_log("PIT %i: M = %i, RM/WM = %i, State = %i, Out = %i\n", t, ctr->m, ctr->rm, ctr->state, ctr->out); + } + ctr->thit = 0; + } + break; + + case 0: + case 1: + case 2: /* the actual timers */ + ctr = &dev->counters[t]; + + switch (ctr->wm) { + case 1: + ctr->l = val; + pitf_ctr_load(ctr); + break; + case 2: + ctr->l = (val << 8); + pitf_ctr_load(ctr); + break; + case 0: + ctr->l &= 0xFF; + ctr->l |= (val << 8); + pitf_ctr_load(ctr); + ctr->wm = 3; + break; + case 3: + ctr->l &= 0xFF00; + ctr->l |= val; + ctr->wm = 0; + break; + } + break; + } +} + +static uint8_t +pitf_read(uint16_t addr, void *priv) +{ + pitf_t *dev = (pitf_t *) priv; + uint8_t ret = 0xff; + int t = (addr & 3); + ctrf_t *ctr; + + switch (addr & 3) { + case 3: /* Control. */ + /* This is 8254-only, 8253 returns 0x00. */ + ret = (dev->flags & PIT_8254) ? dev->ctrl : 0x00; + break; + + case 0: + case 1: + case 2: /* The actual timers. */ + ctr = &dev->counters[t]; + + if (ctr->do_read_status) { + ctr->do_read_status = 0; + ret = ctr->read_status; + break; + } + + if (ctr->rereadlatch && !ctr->latched) { + ctr->rereadlatch = 0; + ctr->rl = pitf_read_timer(ctr); + } + switch (ctr->rm) { + case 0: + ret = ctr->rl >> 8; + ctr->rm = 3; + ctr->latched = 0; + ctr->rereadlatch = 1; + break; + case 1: + ret = (ctr->rl) & 0xFF; + ctr->latched = 0; + ctr->rereadlatch = 1; + break; + case 2: + ret = (ctr->rl) >> 8; + ctr->latched = 0; + ctr->rereadlatch = 1; + break; + case 3: + ret = (ctr->rl) & 0xFF; + if (ctr->m & 0x80) + ctr->m &= 7; + else + ctr->rm = 0; + break; + } + break; + } + + pit_log("[%04X:%08X] pit_read(%04X, %08X) = %02X\n", CS, cpu_state.pc, addr, priv, ret); + + return ret; +} + +static void +pitf_timer_over(void *p) +{ + ctrf_t *ctr = (ctrf_t *) p; + pitf_over(ctr); +} + +static void +pitf_ctr_clock(void *data, int counter_id) +{ + pitf_t *pit = (pitf_t *)data; + ctrf_t *ctr = &pit->counters[counter_id]; + + if (ctr->thit || !ctr->enabled) + return; + + if (ctr->using_timer) + return; + + ctr->count -= (ctr->m == 3) ? 2 : 1; + if (!ctr->count) + pitf_over(ctr); +} + +static void +ctr_reset(ctrf_t *ctr) +{ + ctr->ctrl = 0; + ctr->m = 0; + ctr->gate = 0; + ctr->l = 0xffff; + ctr->thit = 1; + ctr->using_timer = 1; +} + +static void +pitf_reset(pitf_t *dev) +{ + int i; + + memset(dev, 0, sizeof(pitf_t)); + + for (i = 0; i < 3; i++) + ctr_reset(&dev->counters[i]); + + /* Disable speaker gate. */ + dev->counters[2].gate = 0; +} + +static void +pitf_close(void *priv) +{ + pitf_t *dev = (pitf_t *) priv; + + if (dev == pit_devs[0].data) + pit_devs[0].data = NULL; + + if (dev == pit_devs[1].data) + pit_devs[1].data = NULL; + + if (dev != NULL) + free(dev); +} + +static void * +pitf_init(const device_t *info) +{ + pitf_t *dev = (pitf_t *) malloc(sizeof(pitf_t)); + pitf_reset(dev); + + dev->flags = info->local; + + if (!(dev->flags & PIT_PS2) && !(dev->flags & PIT_CUSTOM_CLOCK)) { + for (int i = 0; i < 3; i++) { + ctrf_t *ctr = &dev->counters[i]; + timer_add(&ctr->timer, pitf_timer_over, (void *)ctr, 0); + } + } + + if (!(dev->flags & PIT_EXT_IO)) { + io_sethandler((dev->flags & PIT_SECONDARY) ? 0x0048 : 0x0040, 0x0004, + pitf_read, NULL, NULL, pitf_write, NULL, NULL, dev); + } + + return dev; +} + +const device_t i8253_fast_device = { + .name = "Intel 8253/8253-5 Programmable Interval Timer", + .internal_name = "i8253_fast", + .flags = DEVICE_ISA, + .local = PIT_8253, + .init = pitf_init, + .close = pitf_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + +const device_t i8254_fast_device = { + .name = "Intel 8254 Programmable Interval Timer", + .internal_name = "i8254_fast", + .flags = DEVICE_ISA, + .local = PIT_8254, + .init = pitf_init, + .close = pitf_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + +const device_t i8254_sec_fast_device = { + .name = "Intel 8254 Programmable Interval Timer (Secondary)", + .internal_name = "i8254_sec_fast", + .flags = DEVICE_ISA, + .local = PIT_8254 | PIT_SECONDARY, + .init = pitf_init, + .close = pitf_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + +const device_t i8254_ext_io_fast_device = { + .name = "Intel 8254 Programmable Interval Timer (External I/O)", + .internal_name = "i8254_ext_io_fast", + .flags = DEVICE_ISA, + .local = PIT_8254 | PIT_EXT_IO, + .init = pitf_init, + .close = pitf_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + +const device_t i8254_ps2_fast_device = { + .name = "Intel 8254 Programmable Interval Timer (PS/2)", + .internal_name = "i8254_ps2_fast", + .flags = DEVICE_ISA, + .local = PIT_8254 | PIT_PS2 | PIT_EXT_IO, + .init = pitf_init, + .close = pitf_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + +const pit_intf_t pit_fast_intf = { + &pitf_read, + &pitf_write, + &pitf_ctr_get_count, + &pitf_ctr_set_gate, + &pitf_ctr_set_using_timer, + &pitf_ctr_set_out_func, + &pitf_ctr_set_load_func, + &pitf_ctr_clock, + NULL, +}; \ No newline at end of file diff --git a/src/port_6x.c b/src/port_6x.c index c583ffdf0..0fe168a3c 100644 --- a/src/port_6x.c +++ b/src/port_6x.c @@ -63,7 +63,7 @@ port_6x_write(uint16_t port, uint8_t val, void *priv) speaker_enable = val & 2; if (speaker_enable) was_speaker_enable = 1; - pit_ctr_set_gate(&pit->counters[2], val & 1); + pit_devs[0].set_gate(pit_devs[0].data, 2, val & 1); if (dev->flags & PORT_6X_TURBO) xi8088_turbo_set(!!(val & 0x04)); diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index b9d0e40e9..0cb4560c4 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -178,6 +178,7 @@ endif() if(WIN32) enable_language(RC) target_sources(86Box PUBLIC ../win/86Box-qt.rc) + target_sources(plat PRIVATE win_dynld.c) target_sources(plat PRIVATE win_joystick_rawinput.c) target_sources(ui PRIVATE qt_d3d9renderer.hpp qt_d3d9renderer.cpp) target_link_libraries(86Box hid d3d9) diff --git a/src/qt/languages/pt-BR.po b/src/qt/languages/pt-BR.po index 7d1eecb09..b7f325a9a 100644 --- a/src/qt/languages/pt-BR.po +++ b/src/qt/languages/pt-BR.po @@ -116,7 +116,7 @@ msgid "VGA screen &type" msgstr "&Tipo de tela VGA" msgid "RGB &Color" -msgstr "&Cor RGB" +msgstr "&Cores RGB" msgid "&RGB Grayscale" msgstr "Tons de cinza &RGB" @@ -347,13 +347,13 @@ msgid "Time synchronization" msgstr "Sincronização da hora" msgid "Disabled" -msgstr "Desativada" +msgstr "Desativar" msgid "Enabled (local time)" -msgstr "Ativada (hora local)" +msgstr "Ativar (hora local)" msgid "Enabled (UTC)" -msgstr "Ativada (UTC)" +msgstr "Ativar (UTC)" msgid "Dynamic Recompiler" msgstr "Recompilador dinâmico" @@ -386,10 +386,10 @@ msgid "Sound card:" msgstr "Placa de som:" msgid "MIDI Out Device:" -msgstr "Disp. saída MIDI:" +msgstr "Disp. de saída MIDI:" msgid "MIDI In Device:" -msgstr "Disp. entrada MIDI:" +msgstr "Disp. de entrada MIDI:" msgid "Standalone MPU-401" msgstr "MPU-401 autônomo" @@ -506,7 +506,7 @@ msgid "&Remove" msgstr "&Remover" msgid "Bus:" -msgstr "Bar.:" +msgstr "Barramento:" msgid "Channel:" msgstr "Canal:" @@ -536,7 +536,7 @@ msgid "Image Format:" msgstr "Formato:" msgid "Block Size:" -msgstr "Bloco:" +msgstr "Blocos:" msgid "Floppy drives:" msgstr "Unidades de disquete:" @@ -599,7 +599,7 @@ msgid "Fatal error" msgstr "Erro fatal" msgid " - PAUSED" -msgstr " - PAUSED" +msgstr " - PAUSADO" msgid "Press Ctrl+Alt+PgDn to return to windowed mode." msgstr "Use Ctrl+Alt+PgDn para retornar ao modo janela" @@ -749,7 +749,7 @@ msgid "Microsoft SideWinder Pad" msgstr "Microsoft SideWinder Pad" msgid "Thrustmaster Flight Control System" -msgstr "Thrustmaster Flight Control System" +msgstr "Sistema de Controle de Voo Thrustmaster" msgid "None" msgstr "Nada" @@ -821,7 +821,7 @@ msgid "About 86Box" msgstr "Sobre o 86Box" msgid "86Box v" -msgstr "86Box versão" +msgstr "86Box versão " msgid "An emulator of old computers\n\nAuthors: Sarah Walker, Miran Grca, Fred N. van Kempen (waltje), SA1988, Tiseno100, reenigne, leilei, JohnElliott, greatpsycho, and others.\n\nReleased under the GNU General Public License version 2 or later. See LICENSE for more information." msgstr "Um emulador de computadores antigos\n\nAutores: Sarah Walker, Miran Grca, Fred N. van Kempen (waltje), SA1988, Tiseno100, reenigne, leilei, JohnElliott, greatpsycho, e outros.\n\nTraduzido por: Altieres Lima da Silva\n\nLançado sob a Licença Pública Geral GNU versão 2 ou posterior. Veja o arquivo LICENSE para mais informações." diff --git a/src/qt/qt_platform.cpp b/src/qt/qt_platform.cpp index 91f76d900..89ead7acd 100644 --- a/src/qt/qt_platform.cpp +++ b/src/qt/qt_platform.cpp @@ -54,6 +54,7 @@ QElapsedTimer elapsed_timer; static std::atomic_int blitmx_contention = 0; static std::recursive_mutex blitmx; +static thread_local std::unique_lock blit_lock { blitmx, std::defer_lock }; class CharPointer { public: @@ -431,6 +432,7 @@ void plat_language_code_r(uint32_t lcid, char* outbuf, int len) { return; } +#ifndef Q_OS_WINDOWS void* dynld_module(const char *name, dllimp_t *table) { QString libraryName = name; @@ -462,21 +464,22 @@ void dynld_close(void *handle) { delete reinterpret_cast(handle); } +#endif void startblit() { blitmx_contention++; - if (blitmx.try_lock()) { + if (blit_lock.try_lock()) { return; } - blitmx.lock(); + blit_lock.lock(); } void endblit() { blitmx_contention--; - blitmx.unlock(); + blit_lock.unlock(); if (blitmx_contention > 0) { // a deadlock has been observed on linux when toggling via video_toggle_option // because the mutex is typically unfair on linux diff --git a/src/qt/win_dynld.c b/src/qt/win_dynld.c new file mode 100644 index 000000000..98eb4739f --- /dev/null +++ b/src/qt/win_dynld.c @@ -0,0 +1,87 @@ +/* + * 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. + * + * Try to load a support DLL. + * + * + * + * Author: Fred N. van Kempen, + * + * Copyright 2017,2018 Fred N. van Kempen + */ +#include +#include +#include +#include +#include +#include +#include +#define HAVE_STDARG_H +#include <86box/86box.h> +#include <86box/plat_dynld.h> + + +#ifdef ENABLE_DYNLD_LOG +int dynld_do_log = ENABLE_DYNLD_LOG; + + +static void +dynld_log(const char *fmt, ...) +{ + va_list ap; + + if (dynld_do_log) { + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); + } +} +#else +#define dynld_log(fmt, ...) +#endif + + +void * +dynld_module(const char *name, dllimp_t *table) +{ + HMODULE h; + dllimp_t *imp; + void *func; + + /* See if we can load the desired module. */ + if ((h = LoadLibrary(name)) == NULL) { + dynld_log("DynLd(\"%s\"): library not found! (%08X)\n", name, GetLastError()); + return(NULL); + } + + /* Now load the desired function pointers. */ + for (imp=table; imp->name!=NULL; imp++) { + func = GetProcAddress(h, imp->name); + if (func == NULL) { + dynld_log("DynLd(\"%s\"): function '%s' not found! (%08X)\n", + name, imp->name, GetLastError()); + FreeLibrary(h); + return(NULL); + } + + /* To overcome typing issues.. */ + *(char **)imp->func = (char *)func; + } + + /* All good. */ + dynld_log("loaded %s\n", name); + return((void *)h); +} + + +void +dynld_close(void *handle) +{ + if (handle != NULL) + FreeLibrary((HMODULE)handle); +} diff --git a/src/sio/sio_ali5123.c b/src/sio/sio_ali5123.c index cabe38a58..79d9219ba 100644 --- a/src/sio/sio_ali5123.c +++ b/src/sio/sio_ali5123.c @@ -24,6 +24,7 @@ #include <86box/device.h> #include <86box/pic.h> #include <86box/pci.h> +#include <86box/keyboard.h> #include <86box/lpt.h> #include <86box/serial.h> #include <86box/hdc.h> @@ -456,6 +457,8 @@ ali5123_init(const device_t *info) io_sethandler(FDC_PRIMARY_ADDR, 0x0002, ali5123_read, NULL, NULL, ali5123_write, NULL, NULL, dev); + device_add(&keyboard_ps2_ali_pci_device); + return dev; } diff --git a/src/sound/CMakeLists.txt b/src/sound/CMakeLists.txt index c0aaa2790..581f8d517 100644 --- a/src/sound/CMakeLists.txt +++ b/src/sound/CMakeLists.txt @@ -13,7 +13,7 @@ # Copyright 2020,2021 David Hrdlička. # -add_library(snd OBJECT sound.c snd_opl.c snd_opl_nuked.c snd_resid.cc +add_library(snd OBJECT sound.c snd_opl.c snd_opl_nuked.c snd_opl_ymfm.cpp snd_resid.cc midi.c snd_speaker.c snd_pssj.c snd_lpt_dac.c snd_ac97_codec.c snd_ac97_via.c snd_lpt_dss.c snd_ps1.c snd_adlib.c snd_adlibgold.c snd_ad1848.c snd_audiopci.c snd_azt2316a.c snd_cms.c snd_cmi8x38.c snd_cs423x.c snd_gus.c snd_sb.c snd_sb_dsp.c @@ -105,6 +105,9 @@ if(MUNT) endif() endif() +add_subdirectory(ymfm) +target_link_libraries(86Box ymfm) + if(PAS16) target_compile_definitions(snd PRIVATE USE_PAS16) target_sources(snd PRIVATE snd_pas16.c) diff --git a/src/sound/snd_adlib.c b/src/sound/snd_adlib.c index 1592131d0..d4bc1e3ca 100644 --- a/src/sound/snd_adlib.c +++ b/src/sound/snd_adlib.c @@ -33,7 +33,7 @@ adlib_log(const char *fmt, ...) #endif typedef struct adlib_t { - opl_t opl; + fm_drv_t opl; uint8_t pos_regs[8]; } adlib_t; @@ -44,12 +44,12 @@ adlib_get_buffer(int32_t *buffer, int len, void *p) adlib_t *adlib = (adlib_t *) p; int c; - opl2_update(&adlib->opl); + int32_t *opl_buf = adlib->opl.update(adlib->opl.priv); for (c = 0; c < len * 2; c++) - buffer[c] += (int32_t) adlib->opl.buffer[c]; + buffer[c] += (int32_t) opl_buf[c]; - adlib->opl.pos = 0; + adlib->opl.reset_buffer(adlib->opl.priv); } uint8_t @@ -76,14 +76,14 @@ adlib_mca_write(int port, uint8_t val, void *p) case 0x102: if ((adlib->pos_regs[2] & 1) && !(val & 1)) io_removehandler(0x0388, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &adlib->opl); + adlib->opl.read, NULL, NULL, + adlib->opl.write, NULL, NULL, + adlib->opl.priv); if (!(adlib->pos_regs[2] & 1) && (val & 1)) io_sethandler(0x0388, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &adlib->opl); + adlib->opl.read, NULL, NULL, + adlib->opl.write, NULL, NULL, + adlib->opl.priv); break; } adlib->pos_regs[port & 7] = val; @@ -104,11 +104,11 @@ adlib_init(const device_t *info) memset(adlib, 0, sizeof(adlib_t)); adlib_log("adlib_init\n"); - opl2_init(&adlib->opl); + fm_driver_get(FM_YM3812, &adlib->opl); io_sethandler(0x0388, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &adlib->opl); + adlib->opl.read, NULL, NULL, + adlib->opl.write, NULL, NULL, + adlib->opl.priv); sound_add_handler(adlib_get_buffer, adlib); return adlib; } @@ -119,9 +119,9 @@ adlib_mca_init(const device_t *info) adlib_t *adlib = adlib_init(info); io_removehandler(0x0388, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &adlib->opl); + adlib->opl.read, NULL, NULL, + adlib->opl.write, NULL, NULL, + adlib->opl.priv); mca_add(adlib_mca_read, adlib_mca_write, adlib_mca_feedb, @@ -137,7 +137,6 @@ void adlib_close(void *p) { adlib_t *adlib = (adlib_t *) p; - free(adlib); } diff --git a/src/sound/snd_adlibgold.c b/src/sound/snd_adlibgold.c index 652c672e2..a15174b45 100644 --- a/src/sound/snd_adlibgold.c +++ b/src/sound/snd_adlibgold.c @@ -20,6 +20,7 @@ typedef struct adgold_t { int adgold_irq_status; + int irq, dma, hdma; uint8_t adgold_eeprom[0x1a]; @@ -54,7 +55,7 @@ typedef struct adgold_t { int voice_count[2], voice_latch[2]; } adgold_mma; - opl_t opl; + fm_drv_t opl; ym7128_t ym7128; int fm_vol_l, fm_vol_r; @@ -157,7 +158,7 @@ adgold_update_irq_status(adgold_t *adgold) adgold->adgold_status = temp; if ((adgold->adgold_status ^ 0xf) && !adgold->adgold_irq_status) { - picint(0x80); + picint(1 << adgold->irq); } adgold->adgold_irq_status = adgold->adgold_status ^ 0xf; @@ -167,23 +168,26 @@ void adgold_getsamp_dma(adgold_t *adgold, int channel) { int temp; + dma_set_drq(adgold->dma, 1); if ((adgold->adgold_mma_regs[channel][0xc] & 0x60) && (((adgold->adgold_mma_fifo_end[channel] - adgold->adgold_mma_fifo_start[channel]) & 255) >= 127)) return; - temp = dma_channel_read(1); - if (temp == DMA_NODATA) + temp = dma_channel_read(adgold->dma); + if (temp == DMA_NODATA) { return; + } adgold->adgold_mma_fifo[channel][adgold->adgold_mma_fifo_end[channel]] = temp; - adgold->adgold_mma_fifo_end[channel] = (adgold->adgold_mma_fifo_end[channel] + 1) & 255; + adgold->adgold_mma_fifo_end[channel] = (adgold->adgold_mma_fifo_end[channel] + 1) & 255; if (adgold->adgold_mma_regs[channel][0xc] & 0x60) { - temp = dma_channel_read(1); + temp = dma_channel_read(adgold->dma); adgold->adgold_mma_fifo[channel][adgold->adgold_mma_fifo_end[channel]] = temp; - adgold->adgold_mma_fifo_end[channel] = (adgold->adgold_mma_fifo_end[channel] + 1) & 255; + adgold->adgold_mma_fifo_end[channel] = (adgold->adgold_mma_fifo_end[channel] + 1) & 255; } if (((adgold->adgold_mma_fifo_end[channel] - adgold->adgold_mma_fifo_start[channel]) & 255) >= adgold->adgold_mma_intpos[channel]) { adgold->adgold_mma_status &= ~(0x01 << channel); adgold_update_irq_status(adgold); + dma_set_drq(adgold->dma, 0); } } @@ -194,7 +198,7 @@ adgold_write(uint16_t addr, uint8_t val, void *p) switch (addr & 7) { case 0: case 1: - opl3_write(addr, val, &adgold->opl); + adgold->opl.write(addr, val, adgold->opl.priv); break; case 2: @@ -209,7 +213,7 @@ adgold_write(uint16_t addr, uint8_t val, void *p) if (adgold->adgold_38x_state) /*Write to control chip*/ adgold->adgold_38x_addr = val; else - opl3_write(addr, val, &adgold->opl); + adgold->opl.write(addr, val, adgold->opl.priv); break; case 3: if (adgold->adgold_38x_state) { @@ -275,7 +279,7 @@ adgold_write(uint16_t addr, uint8_t val, void *p) break; } } else - opl3_write(addr, val, &adgold->opl); + adgold->opl.write(addr, val, adgold->opl.priv); break; case 4: case 6: @@ -335,10 +339,11 @@ adgold_write(uint16_t addr, uint8_t val, void *p) break; /* 7350 Hz*/ } if (val & 0x80) { - adgold->adgold_mma_enable[0] = 0; + adgold->adgold_mma_enable[0] = 0; adgold->adgold_mma_fifo_end[0] = adgold->adgold_mma_fifo_start[0] = 0; adgold->adgold_mma_status &= ~0x01; adgold_update_irq_status(adgold); + dma_set_drq(adgold->dma, 0); } if ((val & 0x01)) /*Start playback*/ { @@ -347,7 +352,7 @@ adgold_write(uint16_t addr, uint8_t val, void *p) if (adgold->adgold_mma_regs[0][0xc] & 1) { if (adgold->adgold_mma_regs[0][0xc] & 0x80) { - adgold->adgold_mma_enable[1] = 1; + adgold->adgold_mma_enable[1] = 1; adgold->adgold_mma.voice_count[1] = adgold->adgold_mma.voice_latch[1]; while (((adgold->adgold_mma_fifo_end[0] - adgold->adgold_mma_fifo_start[0]) & 255) < 128) { @@ -357,10 +362,12 @@ adgold_write(uint16_t addr, uint8_t val, void *p) if (((adgold->adgold_mma_fifo_end[0] - adgold->adgold_mma_fifo_start[0]) & 255) >= adgold->adgold_mma_intpos[0]) { adgold->adgold_mma_status &= ~0x01; adgold_update_irq_status(adgold); + dma_set_drq(adgold->dma, 0); } if (((adgold->adgold_mma_fifo_end[1] - adgold->adgold_mma_fifo_start[1]) & 255) >= adgold->adgold_mma_intpos[1]) { adgold->adgold_mma_status &= ~0x02; adgold_update_irq_status(adgold); + dma_set_drq(adgold->dma, 0); } } else { while (((adgold->adgold_mma_fifo_end[0] - adgold->adgold_mma_fifo_start[0]) & 255) < 128) { @@ -369,6 +376,7 @@ adgold_write(uint16_t addr, uint8_t val, void *p) if (((adgold->adgold_mma_fifo_end[0] - adgold->adgold_mma_fifo_start[0]) & 255) >= adgold->adgold_mma_intpos[0]) { adgold->adgold_mma_status &= ~0x01; adgold_update_irq_status(adgold); + dma_set_drq(adgold->dma, 0); } } } @@ -379,10 +387,11 @@ adgold_write(uint16_t addr, uint8_t val, void *p) case 0xb: if (((adgold->adgold_mma_fifo_end[0] - adgold->adgold_mma_fifo_start[0]) & 255) < 128) { adgold->adgold_mma_fifo[0][adgold->adgold_mma_fifo_end[0]] = val; - adgold->adgold_mma_fifo_end[0] = (adgold->adgold_mma_fifo_end[0] + 1) & 255; + adgold->adgold_mma_fifo_end[0] = (adgold->adgold_mma_fifo_end[0] + 1) & 255; if (((adgold->adgold_mma_fifo_end[0] - adgold->adgold_mma_fifo_start[0]) & 255) >= adgold->adgold_mma_intpos[0]) { adgold->adgold_mma_status &= ~0x01; adgold_update_irq_status(adgold); + dma_set_drq(adgold->dma, 0); } } break; @@ -457,6 +466,7 @@ adgold_write(uint16_t addr, uint8_t val, void *p) adgold->adgold_mma_fifo_end[1] = adgold->adgold_mma_fifo_start[1] = 0; adgold->adgold_mma_status &= ~0x02; adgold_update_irq_status(adgold); + dma_set_drq(adgold->dma, 0); } if ((val & 0x01)) /*Start playback*/ { @@ -479,6 +489,7 @@ adgold_write(uint16_t addr, uint8_t val, void *p) if (((adgold->adgold_mma_fifo_end[1] - adgold->adgold_mma_fifo_start[1]) & 255) >= adgold->adgold_mma_intpos[1]) { adgold->adgold_mma_status &= ~0x02; adgold_update_irq_status(adgold); + dma_set_drq(adgold->dma, 0); } } break; @@ -501,14 +512,14 @@ adgold_read(uint16_t addr, void *p) switch (addr & 7) { case 0: case 1: - temp = opl3_read(addr, &adgold->opl); + temp = adgold->opl.read(addr, adgold->opl.priv); break; case 2: if (adgold->adgold_38x_state) /*Read from control chip*/ temp = adgold->adgold_status; else - temp = opl3_read(addr, &adgold->opl); + temp = adgold->opl.read(addr, adgold->opl.priv); break; case 3: @@ -525,9 +536,10 @@ adgold_read(uint16_t addr, void *p) default: temp = adgold->adgold_38x_regs[adgold->adgold_38x_addr]; + break; } } else - temp = opl3_read(addr, &adgold->opl); + temp = adgold->opl.read(addr, adgold->opl.priv); break; case 4: @@ -713,13 +725,13 @@ adgold_get_buffer(int32_t *buffer, int len, void *p) int c; - opl3_update(&adgold->opl); + int32_t *opl_buf = adgold->opl.update(adgold->opl.priv); adgold_update(adgold); for (c = 0; c < len * 2; c += 2) { - adgold_buffer[c] = ((adgold->opl.buffer[c] * adgold->fm_vol_l) >> 7) / 2; + adgold_buffer[c] = ((opl_buf[c] * adgold->fm_vol_l) >> 7) / 2; adgold_buffer[c] += ((adgold->mma_buffer[0][c >> 1] * adgold->samp_vol_l) >> 7) / 4; - adgold_buffer[c + 1] = ((adgold->opl.buffer[c + 1] * adgold->fm_vol_r) >> 7) / 2; + adgold_buffer[c + 1] = ((opl_buf[c + 1] * adgold->fm_vol_r) >> 7) / 2; adgold_buffer[c + 1] += ((adgold->mma_buffer[1][c >> 1] * adgold->samp_vol_r) >> 7) / 4; } @@ -808,7 +820,7 @@ adgold_get_buffer(int32_t *buffer, int len, void *p) buffer[c + 1] += temp; } - adgold->opl.pos = 0; + adgold->opl.reset_buffer(adgold->opl.priv); adgold->pos = 0; free(adgold_buffer); @@ -877,11 +889,12 @@ adgold_init(const device_t *info) adgold_t *adgold = malloc(sizeof(adgold_t)); memset(adgold, 0, sizeof(adgold_t)); + adgold->dma = device_get_config_int("dma"); + adgold->irq = device_get_config_int("irq"); adgold->surround_enabled = device_get_config_int("surround"); - adgold->gameport_enabled = device_get_config_int("gameport"); - opl3_init(&adgold->opl); + fm_driver_get(FM_YMF262, &adgold->opl); if (adgold->surround_enabled) ym7128_init(&adgold->ym7128); @@ -912,9 +925,9 @@ adgold_init(const device_t *info) adgold->adgold_eeprom[0x10] = 0xff; adgold->adgold_eeprom[0x11] = 0x20; adgold->adgold_eeprom[0x12] = 0x00; - adgold->adgold_eeprom[0x13] = 0x0b; /* IRQ 1, DMA1 */ - adgold->adgold_eeprom[0x14] = 0x00; /* DMA2 */ - adgold->adgold_eeprom[0x15] = 0x71; /* Port */ + adgold->adgold_eeprom[0x13] = 0xa0; + adgold->adgold_eeprom[0x14] = 0x00; + adgold->adgold_eeprom[0x15] = 0x388 / 8; /*Present at 388-38f*/ adgold->adgold_eeprom[0x16] = 0x00; adgold->adgold_eeprom[0x17] = 0x68; adgold->adgold_eeprom[0x18] = 0x00; /* Surround */ @@ -927,25 +940,36 @@ adgold_init(const device_t *info) fclose(f); } - adgold->adgold_status = 0xf; - adgold->adgold_38x_addr = 0; - adgold->adgold_eeprom[0x13] = 3 | (1 << 3); /*IRQ 7, DMA 1*/ - // adgold->adgold_eeprom[0x14] = 3 << 4; /*DMA 3 - Double check this */ - adgold->adgold_eeprom[0x14] = 0x00; /*DMA ?*/ - adgold->adgold_eeprom[0x15] = 0x388 / 8; /*Present at 388-38f*/ + adgold->adgold_status = 0xf; + adgold->adgold_38x_addr = 0; + switch (adgold->irq) { + case 3: + adgold->adgold_eeprom[0x13] |= 0x00; + break; + case 4: + adgold->adgold_eeprom[0x13] |= 0x01; + break; + case 5: + adgold->adgold_eeprom[0x13] |= 0x02; + break; + case 7: + adgold->adgold_eeprom[0x13] |= 0x03; + break; + } + adgold->adgold_eeprom[0x13] |= (adgold->dma << 3); memcpy(adgold->adgold_38x_regs, adgold->adgold_eeprom, 0x19); - adgold->vol_l = attenuation[adgold->adgold_eeprom[0x04] & 0x3f]; - adgold->vol_r = attenuation[adgold->adgold_eeprom[0x05] & 0x3f]; - adgold->bass = adgold->adgold_eeprom[0x06] & 0xf; - adgold->treble = adgold->adgold_eeprom[0x07] & 0xf; - adgold->fm_vol_l = (int) (int8_t) (adgold->adgold_eeprom[0x09] - 128); - adgold->fm_vol_r = (int) (int8_t) (adgold->adgold_eeprom[0x0a] - 128); + adgold->vol_l = attenuation[adgold->adgold_eeprom[0x04] & 0x3f]; + adgold->vol_r = attenuation[adgold->adgold_eeprom[0x05] & 0x3f]; + adgold->bass = adgold->adgold_eeprom[0x06] & 0xf; + adgold->treble = adgold->adgold_eeprom[0x07] & 0xf; + adgold->fm_vol_l = (int) (int8_t) (adgold->adgold_eeprom[0x09] - 128); + adgold->fm_vol_r = (int) (int8_t) (adgold->adgold_eeprom[0x0a] - 128); adgold->samp_vol_l = (int) (int8_t) (adgold->adgold_eeprom[0x0b] - 128); adgold->samp_vol_r = (int) (int8_t) (adgold->adgold_eeprom[0x0c] - 128); adgold->aux_vol_l = (int) (int8_t) (adgold->adgold_eeprom[0x0d] - 128); adgold->aux_vol_r = (int) (int8_t) (adgold->adgold_eeprom[0x0e] - 128); - adgold->adgold_mma_enable[0] = 0; + adgold->adgold_mma_enable[0] = 0; adgold->adgold_mma_fifo_start[0] = adgold->adgold_mma_fifo_end[0] = 0; /*388/389 are handled by adlib_init*/ @@ -982,6 +1006,54 @@ adgold_close(void *p) static const device_config_t adgold_config[] = { // clang-format off + { + .name = "irq", + .description = "IRQ", + .type = CONFIG_SELECTION, + .default_string = "", + .default_int = 7, + .file_filter = "", + .spinner = { 0 }, + .selection = { + { + .description = "IRQ 3", + .value = 3 + }, + { + .description = "IRQ 4", + .value = 4 + }, + { + .description = "IRQ 5", + .value = 5 + }, + { + .description = "IRQ 7", + .value = 7 + }, + { .description = "" } + } + }, + { + .name = "dma", + .description = "Low DMA channel", + .type = CONFIG_SELECTION, + .default_string = "", + .default_int = 1, + .file_filter = "", + .spinner = { 0 }, + .selection = { + { + .description = "DMA 1", + .value = 1 + }, + { + .description = "DMA 3", + .value = 3 + }, + { .description = "" } + } + }, { .name = "gameport", .description = "Enable Game port", diff --git a/src/sound/snd_azt2316a.c b/src/sound/snd_azt2316a.c index a72e1dbe5..890734450 100644 --- a/src/sound/snd_azt2316a.c +++ b/src/sound/snd_azt2316a.c @@ -1149,7 +1149,7 @@ azt_init(const device_t *info) azt2316a->sb->dsp.azt_eeprom[i] = read_eeprom[i]; if (azt2316a->sb->opl_enabled) - opl3_init(&azt2316a->sb->opl); + fm_driver_get(FM_YMF262, &azt2316a->sb->opl); sb_dsp_init(&azt2316a->sb->dsp, SBPRO2, azt2316a->type, azt2316a); sb_dsp_setaddr(&azt2316a->sb->dsp, azt2316a->cur_addr); @@ -1158,9 +1158,9 @@ azt_init(const device_t *info) sb_ct1345_mixer_reset(azt2316a->sb); /* DSP I/O handler is activated in sb_dsp_setaddr */ if (azt2316a->sb->opl_enabled) { - io_sethandler(azt2316a->cur_addr + 0, 0x0004, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &azt2316a->sb->opl); - io_sethandler(azt2316a->cur_addr + 8, 0x0002, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &azt2316a->sb->opl); - io_sethandler(0x0388, 0x0004, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &azt2316a->sb->opl); + io_sethandler(azt2316a->cur_addr + 0, 0x0004, azt2316a->sb->opl.read, NULL, NULL, azt2316a->sb->opl.write, NULL, NULL, azt2316a->sb->opl.priv); + io_sethandler(azt2316a->cur_addr + 8, 0x0002, azt2316a->sb->opl.read, NULL, NULL, azt2316a->sb->opl.write, NULL, NULL, azt2316a->sb->opl.priv); + io_sethandler(0x0388, 0x0004, azt2316a->sb->opl.read, NULL, NULL, azt2316a->sb->opl.write, NULL, NULL, azt2316a->sb->opl.priv); } io_sethandler(azt2316a->cur_addr + 4, 0x0002, sb_ct1345_mixer_read, NULL, NULL, sb_ct1345_mixer_write, NULL, NULL, azt2316a->sb); diff --git a/src/sound/snd_cmi8x38.c b/src/sound/snd_cmi8x38.c index 4ce76508e..d4a54880b 100644 --- a/src/sound/snd_cmi8x38.c +++ b/src/sound/snd_cmi8x38.c @@ -473,10 +473,10 @@ static void cmi8x38_remap_sb(cmi8x38_t *dev) { if (dev->sb_base) { - io_removehandler(dev->sb_base, 0x0004, opl3_read, NULL, NULL, - opl3_write, NULL, NULL, &dev->sb->opl); - io_removehandler(dev->sb_base + 8, 0x0002, opl3_read, NULL, NULL, - opl3_write, NULL, NULL, &dev->sb->opl); + io_removehandler(dev->sb_base, 0x0004, dev->sb->opl.read, NULL, NULL, + dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); + io_removehandler(dev->sb_base + 8, 0x0002, dev->sb->opl.read, NULL, NULL, + dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); io_removehandler(dev->sb_base + 4, 0x0002, cmi8x38_sb_mixer_read, NULL, NULL, cmi8x38_sb_mixer_write, NULL, NULL, dev); @@ -493,10 +493,10 @@ cmi8x38_remap_sb(cmi8x38_t *dev) cmi8x38_log("CMI8x38: remap_sb(%04X)\n", dev->sb_base); if (dev->sb_base) { - io_sethandler(dev->sb_base, 0x0004, opl3_read, NULL, NULL, - opl3_write, NULL, NULL, &dev->sb->opl); - io_sethandler(dev->sb_base + 8, 0x0002, opl3_read, NULL, NULL, - opl3_write, NULL, NULL, &dev->sb->opl); + io_sethandler(dev->sb_base, 0x0004, dev->sb->opl.read, NULL, NULL, + dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); + io_sethandler(dev->sb_base + 8, 0x0002, dev->sb->opl.read, NULL, NULL, + dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); io_sethandler(dev->sb_base + 4, 0x0002, cmi8x38_sb_mixer_read, NULL, NULL, cmi8x38_sb_mixer_write, NULL, NULL, dev); @@ -508,8 +508,8 @@ static void cmi8x38_remap_opl(cmi8x38_t *dev) { if (dev->opl_base) { - io_removehandler(dev->opl_base, 0x0004, opl3_read, NULL, NULL, - opl3_write, NULL, NULL, &dev->sb->opl); + io_removehandler(dev->opl_base, 0x0004, dev->sb->opl.read, NULL, NULL, + dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); } dev->opl_base = (dev->type == CMEDIA_CMI8338) ? 0x388 : opl_ports_cmi8738[dev->io_regs[0x17] & 0x03]; @@ -520,8 +520,8 @@ cmi8x38_remap_opl(cmi8x38_t *dev) cmi8x38_log("CMI8x38: remap_opl(%04X)\n", dev->opl_base); if (dev->opl_base) { - io_sethandler(dev->opl_base, 0x0004, opl3_read, NULL, NULL, - opl3_write, NULL, NULL, &dev->sb->opl); + io_sethandler(dev->opl_base, 0x0004, dev->sb->opl.read, NULL, NULL, + dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); } } @@ -593,7 +593,7 @@ cmi8x38_read(uint16_t addr, void *priv) if (dev->type == CMEDIA_CMI8338) goto io_reg; else - ret = opl3_read(addr, &dev->sb->opl); + ret = dev->sb->opl.read(addr, dev->sb->opl.priv); break; case 0x80: @@ -872,7 +872,7 @@ cmi8x38_write(uint16_t addr, uint8_t val, void *priv) case 0x50 ... 0x5f: if (dev->type != CMEDIA_CMI8338) - opl3_write(addr, val, &dev->sb->opl); + dev->sb->opl.write(addr, val, dev->sb->opl.priv); return; case 0x92: diff --git a/src/sound/snd_cs423x.c b/src/sound/snd_cs423x.c index 1f366e373..31f4e8e6c 100644 --- a/src/sound/snd_cs423x.c +++ b/src/sound/snd_cs423x.c @@ -505,18 +505,19 @@ cs423x_get_buffer(int32_t *buffer, int len, void *priv) { cs423x_t *dev = (cs423x_t *) priv; int c, opl_wss = dev->opl_wss; + int32_t *opl_buf = NULL; /* Output audio from the WSS codec, and also the OPL if we're in charge of it. */ ad1848_update(&dev->ad1848); if (opl_wss) - opl3_update(&dev->sb->opl); + opl_buf = dev->sb->opl.update(dev->sb->opl.priv); /* Don't output anything if the analog section is powered down. */ if (!(dev->indirect_regs[2] & 0xa4)) { for (c = 0; c < len * 2; c += 2) { if (opl_wss) { - buffer[c] += (dev->sb->opl.buffer[c] * dev->ad1848.fm_vol_l) >> 16; - buffer[c + 1] += (dev->sb->opl.buffer[c + 1] * dev->ad1848.fm_vol_r) >> 16; + buffer[c] += (opl_buf[c] * dev->ad1848.fm_vol_l) >> 16; + buffer[c + 1] += (opl_buf[c + 1] * dev->ad1848.fm_vol_r) >> 16; } buffer[c] += dev->ad1848.buffer[c] / 2; @@ -526,7 +527,7 @@ cs423x_get_buffer(int32_t *buffer, int len, void *priv) dev->ad1848.pos = 0; if (opl_wss) - dev->sb->opl.pos = 0; + dev->sb->opl.reset_buffer(dev->sb->opl.priv); } static void @@ -590,14 +591,14 @@ cs423x_pnp_config_changed(uint8_t ld, isapnp_device_config_t *config, void *priv } if (dev->opl_base) { - io_removehandler(dev->opl_base, 4, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &dev->sb->opl); + io_removehandler(dev->opl_base, 4, dev->sb->opl.read, NULL, NULL, dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); dev->opl_base = 0; } if (dev->sb_base) { sb_dsp_setaddr(&dev->sb->dsp, 0); - io_removehandler(dev->sb_base, 4, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &dev->sb->opl); - io_removehandler(dev->sb_base + 8, 2, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &dev->sb->opl); + io_removehandler(dev->sb_base, 4, dev->sb->opl.read, NULL, NULL, dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); + io_removehandler(dev->sb_base + 8, 2, dev->sb->opl.read, NULL, NULL, dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); io_removehandler(dev->sb_base + 4, 2, sb_ct1345_mixer_read, NULL, NULL, sb_ct1345_mixer_write, NULL, NULL, dev->sb); io_removehandler(dev->sb_base, 16, NULL, NULL, NULL, cs423x_ctxswitch_write, NULL, NULL, dev); dev->sb_base = 0; @@ -618,14 +619,14 @@ cs423x_pnp_config_changed(uint8_t ld, isapnp_device_config_t *config, void *priv if (config->io[1].base != ISAPNP_IO_DISABLED) { dev->opl_base = config->io[1].base; - io_sethandler(dev->opl_base, 4, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &dev->sb->opl); + io_sethandler(dev->opl_base, 4, dev->sb->opl.read, NULL, NULL, dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); } if (config->io[2].base != ISAPNP_IO_DISABLED) { dev->sb_base = config->io[2].base; sb_dsp_setaddr(&dev->sb->dsp, dev->sb_base); - io_sethandler(dev->sb_base, 4, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &dev->sb->opl); - io_sethandler(dev->sb_base + 8, 2, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &dev->sb->opl); + io_sethandler(dev->sb_base, 4, dev->sb->opl.read, NULL, NULL, dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); + io_sethandler(dev->sb_base + 8, 2, dev->sb->opl.read, NULL, NULL, dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); io_sethandler(dev->sb_base + 4, 2, sb_ct1345_mixer_read, NULL, NULL, sb_ct1345_mixer_write, NULL, NULL, dev->sb); io_sethandler(dev->sb_base, 16, NULL, NULL, NULL, cs423x_ctxswitch_write, NULL, NULL, dev); } diff --git a/src/sound/snd_opl.c b/src/sound/snd_opl.c index 6cba7b3e8..cce75bf39 100644 --- a/src/sound/snd_opl.c +++ b/src/sound/snd_opl.c @@ -28,285 +28,44 @@ #include "cpu.h" #include <86box/86box.h> +#include <86box/device.h> #include <86box/io.h> -#include <86box/timer.h> #include <86box/sound.h> #include <86box/snd_opl.h> -#include <86box/snd_opl_nuked.h> -enum { - FLAG_CYCLES = 0x02, - FLAG_OPL3 = 0x01 -}; - -enum { - STAT_TMR_OVER = 0x60, - STAT_TMR1_OVER = 0x40, - STAT_TMR2_OVER = 0x20, - STAT_TMR_ANY = 0x80 -}; - -enum { - CTRL_RESET = 0x80, - CTRL_TMR_MASK = 0x60, - CTRL_TMR1_MASK = 0x40, - CTRL_TMR2_MASK = 0x20, - CTRL_TMR2_START = 0x02, - CTRL_TMR1_START = 0x01 -}; - -#ifdef ENABLE_OPL_LOG -int opl_do_log = ENABLE_OPL_LOG; - -static void -opl_log(const char *fmt, ...) -{ - va_list ap; - - if (opl_do_log) { - va_start(ap, fmt); - pclog_ex(fmt, ap); - va_end(ap); - } -} -#else -# define opl_log(fmt, ...) -#endif - -static void -timer_tick(opl_t *dev, int tmr) -{ - dev->timer_cur_count[tmr] = (dev->timer_cur_count[tmr] + 1) & 0xff; - - opl_log("Ticking timer %i, count now %02X...\n", tmr, dev->timer_cur_count[tmr]); - - if (dev->timer_cur_count[tmr] == 0x00) { - dev->status |= ((STAT_TMR1_OVER >> tmr) & ~dev->timer_ctrl); - dev->timer_cur_count[tmr] = dev->timer_count[tmr]; - - opl_log("Count wrapped around to zero, reloading timer %i (%02X), status = %02X...\n", tmr, (STAT_TMR1_OVER >> tmr), dev->status); - } - - timer_on_auto(&dev->timers[tmr], (tmr == 1) ? 320.0 : 80.0); -} - -static void -timer_control(opl_t *dev, int tmr, int start) -{ - timer_on_auto(&dev->timers[tmr], 0.0); - - if (start) { - opl_log("Loading timer %i count: %02X = %02X\n", tmr, dev->timer_cur_count[tmr], dev->timer_count[tmr]); - dev->timer_cur_count[tmr] = dev->timer_count[tmr]; - if (dev->flags & FLAG_OPL3) - timer_tick(dev, tmr); /* Per the YMF 262 datasheet, OPL3 starts counting immediately, unlike OPL2. */ - else - timer_on_auto(&dev->timers[tmr], (tmr == 1) ? 320.0 : 80.0); - } else { - opl_log("Timer %i stopped\n", tmr); - if (tmr == 1) { - dev->status &= ~STAT_TMR2_OVER; - } else - dev->status &= ~STAT_TMR1_OVER; - } -} - -static void -timer_1(void *priv) -{ - opl_t *dev = (opl_t *) priv; - - timer_tick(dev, 0); -} - -static void -timer_2(void *priv) -{ - opl_t *dev = (opl_t *) priv; - - timer_tick(dev, 1); -} - -static uint8_t -opl_read(opl_t *dev, uint16_t port) -{ - uint8_t ret = 0xff; - - if ((port & 0x0003) == 0x0000) { - ret = dev->status; - if (dev->status & STAT_TMR_OVER) - ret |= STAT_TMR_ANY; - } - - opl_log("OPL statret = %02x, status = %02x\n", ret, dev->status); - - return ret; -} - -static void -opl_write(opl_t *dev, uint16_t port, uint8_t val) -{ - if ((port & 0x0001) == 0x0001) { - nuked_write_reg_buffered(dev->opl, dev->port, val); - - switch (dev->port) { - case 0x02: /* Timer 1 */ - dev->timer_count[0] = val; - opl_log("Timer 0 count now: %i\n", dev->timer_count[0]); - break; - - case 0x03: /* Timer 2 */ - dev->timer_count[1] = val; - opl_log("Timer 1 count now: %i\n", dev->timer_count[1]); - break; - - case 0x04: /* Timer control */ - if (val & CTRL_RESET) { - opl_log("Resetting timer status...\n"); - dev->status &= ~STAT_TMR_OVER; - } else { - dev->timer_ctrl = val; - timer_control(dev, 0, val & CTRL_TMR1_START); - timer_control(dev, 1, val & CTRL_TMR2_START); - opl_log("Status mask now %02X (val = %02X)\n", (val & ~CTRL_TMR_MASK) & CTRL_TMR_MASK, val); - } - break; - } - } else { - dev->port = nuked_write_addr(dev->opl, port, val) & 0x01ff; - - if (!(dev->flags & FLAG_OPL3)) - dev->port &= 0x00ff; - } -} - -void -opl_set_do_cycles(opl_t *dev, int8_t do_cycles) -{ - if (do_cycles) - dev->flags |= FLAG_CYCLES; - else - dev->flags &= ~FLAG_CYCLES; -} - -static void -opl_init(opl_t *dev, int is_opl3) -{ - memset(dev, 0x00, sizeof(opl_t)); - - dev->flags = FLAG_CYCLES; - if (is_opl3) - dev->flags |= FLAG_OPL3; - else - dev->status = 0x06; - - /* Create a NukedOPL object. */ - dev->opl = nuked_init(48000); - - timer_add(&dev->timers[0], timer_1, dev, 0); - timer_add(&dev->timers[1], timer_2, dev, 0); -} - -void -opl_close(opl_t *dev) -{ - /* Release the NukedOPL object. */ - if (dev->opl) { - nuked_close(dev->opl); - dev->opl = NULL; - } -} +static uint32_t fm_dev_inst[FM_DRV_MAX][FM_MAX]; uint8_t -opl2_read(uint16_t port, void *priv) -{ - opl_t *dev = (opl_t *) priv; +fm_driver_get(int chip_id, fm_drv_t *drv) { + switch (chip_id) { + case FM_YM3812: + if (fm_driver == FM_DRV_NUKED) { + *drv = nuked_opl_drv; + drv->priv = device_add_inst(&ym3812_nuked_device, fm_dev_inst[fm_driver][chip_id]++); + } else { + *drv = ymfm_drv; + drv->priv = device_add_inst(&ym3812_ymfm_device, fm_dev_inst[fm_driver][chip_id]++); + } + break; - if (dev->flags & FLAG_CYCLES) - cycles -= ((int) (isa_timing * 8)); + case FM_YMF262: + if (fm_driver == FM_DRV_NUKED) { + *drv = nuked_opl_drv; + drv->priv = device_add_inst(&ymf262_nuked_device, fm_dev_inst[fm_driver][chip_id]++); + } else { + *drv = ymfm_drv; + drv->priv = device_add_inst(&ymf262_ymfm_device, fm_dev_inst[fm_driver][chip_id]++); + } + break; - opl2_update(dev); - opl_log("OPL2 port read = %04x\n", port); + case FM_YMF289B: + *drv = ymfm_drv; + drv->priv = device_add_inst(&ymf289b_ymfm_device, fm_dev_inst[fm_driver][chip_id]++); + break; - return (opl_read(dev, port)); -} - -void -opl2_write(uint16_t port, uint8_t val, void *priv) -{ - opl_t *dev = (opl_t *) priv; - - opl2_update(dev); - - opl_log("OPL2 port write = %04x\n", port); - opl_write(dev, port, val); -} - -void -opl2_init(opl_t *dev) -{ - opl_init(dev, 0); -} - -void -opl2_update(opl_t *dev) -{ - if (dev->pos >= sound_pos_global) { - return; + default: + return 0; } - nuked_generate_stream(dev->opl, - &dev->buffer[dev->pos * 2], - sound_pos_global - dev->pos); - - for (; dev->pos < sound_pos_global; dev->pos++) { - dev->buffer[dev->pos * 2] /= 2; - dev->buffer[(dev->pos * 2) + 1] = dev->buffer[dev->pos * 2]; - } -} - -uint8_t -opl3_read(uint16_t port, void *priv) -{ - opl_t *dev = (opl_t *) priv; - - if (dev->flags & FLAG_CYCLES) - cycles -= ((int) (isa_timing * 8)); - - opl3_update(dev); - - return (opl_read(dev, port)); -} - -void -opl3_write(uint16_t port, uint8_t val, void *priv) -{ - opl_t *dev = (opl_t *) priv; - - opl3_update(dev); - - opl_write(dev, port, val); -} - -void -opl3_init(opl_t *dev) -{ - opl_init(dev, 1); -} - -/* API to sound interface. */ -void -opl3_update(opl_t *dev) -{ - if (dev->pos >= sound_pos_global) - return; - - nuked_generate_stream(dev->opl, - &dev->buffer[dev->pos * 2], - sound_pos_global - dev->pos); - - for (; dev->pos < sound_pos_global; dev->pos++) { - dev->buffer[dev->pos * 2] /= 2; - dev->buffer[(dev->pos * 2) + 1] /= 2; - } -} + return 1; +}; diff --git a/src/sound/snd_opl_nuked.c b/src/sound/snd_opl_nuked.c index 347316e4e..8e1a7e774 100644 --- a/src/sound/snd_opl_nuked.c +++ b/src/sound/snd_opl_nuked.c @@ -46,6 +46,8 @@ #include <86box/snd_opl_nuked.h> #include <86box/sound.h> #include <86box/timer.h> +#include <86box/device.h> +#include <86box/snd_opl.h> #define WRBUF_SIZE 1024 #define WRBUF_DELAY 1 @@ -169,6 +171,56 @@ typedef struct chip { wrbuf_t wrbuf[WRBUF_SIZE]; } nuked_t; +typedef struct { + nuked_t opl; + int8_t flags, pad; + + uint16_t port; + uint8_t status, timer_ctrl; + uint16_t timer_count[2], + timer_cur_count[2]; + + pc_timer_t timers[2]; + + int pos; + int32_t buffer[SOUNDBUFLEN * 2]; +} nuked_drv_t; + +enum { + FLAG_CYCLES = 0x02, + FLAG_OPL3 = 0x01 +}; + +enum { + STAT_TMR_OVER = 0x60, + STAT_TMR1_OVER = 0x40, + STAT_TMR2_OVER = 0x20, + STAT_TMR_ANY = 0x80 +}; + +enum { + CTRL_RESET = 0x80, + CTRL_TMR_MASK = 0x60, + CTRL_TMR1_MASK = 0x40, + CTRL_TMR2_MASK = 0x20, + CTRL_TMR2_START = 0x02, + CTRL_TMR1_START = 0x01 +}; + +#ifdef ENABLE_OPL_LOG +static void +nuked_log(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); +} +#else +# define nuked_log(fmt, ...) +#endif + // logsin table static const uint16_t logsinrom[256] = { 0x859, 0x6c3, 0x607, 0x58b, 0x52e, 0x4e4, 0x4a6, 0x471, @@ -1270,10 +1322,8 @@ nuked_generate(void *priv, int32_t *bufp) } void -nuked_generate_resampled(void *priv, int32_t *bufp) +nuked_generate_resampled(nuked_t *dev, int32_t *bufp) { - nuked_t *dev = (nuked_t *) priv; - while (dev->samplecnt >= dev->rateratio) { dev->oldsamples[0] = dev->samples[0]; dev->oldsamples[1] = dev->samples[1]; @@ -1292,9 +1342,8 @@ nuked_generate_resampled(void *priv, int32_t *bufp) } void -nuked_generate_stream(void *priv, int32_t *sndptr, uint32_t num) +nuked_generate_stream(nuked_t *dev, int32_t *sndptr, uint32_t num) { - nuked_t *dev = (nuked_t *) priv; uint32_t i; for (i = 0; i < num; i++) { @@ -1303,13 +1352,11 @@ nuked_generate_stream(void *priv, int32_t *sndptr, uint32_t num) } } -void * -nuked_init(uint32_t samplerate) +void +nuked_init(nuked_t *dev, uint32_t samplerate) { - nuked_t *dev; uint8_t i; - dev = (nuked_t *) malloc(sizeof(nuked_t)); memset(dev, 0x00, sizeof(nuked_t)); for (i = 0; i < 36; i++) { @@ -1350,14 +1397,222 @@ nuked_init(uint32_t samplerate) dev->rateratio = (samplerate << RSM_FRAC) / 49716; dev->tremoloshift = 4; dev->vibshift = 1; - - return (dev); } -void -nuked_close(void *priv) +static void +nuked_timer_tick(nuked_drv_t *dev, int tmr) { - nuked_t *dev = (nuked_t *) priv; + dev->timer_cur_count[tmr] = (dev->timer_cur_count[tmr] + 1) & 0xff; + nuked_log("Ticking timer %i, count now %02X...\n", tmr, dev->timer_cur_count[tmr]); + + if (dev->timer_cur_count[tmr] == 0x00) { + dev->status |= ((STAT_TMR1_OVER >> tmr) & ~dev->timer_ctrl); + dev->timer_cur_count[tmr] = dev->timer_count[tmr]; + + nuked_log("Count wrapped around to zero, reloading timer %i (%02X), status = %02X...\n", tmr, (STAT_TMR1_OVER >> tmr), dev->status); + } + + timer_on_auto(&dev->timers[tmr], (tmr == 1) ? 320.0 : 80.0); +} + +static void +nuked_timer_control(nuked_drv_t *dev, int tmr, int start) +{ + timer_on_auto(&dev->timers[tmr], 0.0); + + if (start) { + nuked_log("Loading timer %i count: %02X = %02X\n", tmr, dev->timer_cur_count[tmr], dev->timer_count[tmr]); + dev->timer_cur_count[tmr] = dev->timer_count[tmr]; + if (dev->flags & FLAG_OPL3) + nuked_timer_tick(dev, tmr); /* Per the YMF 262 datasheet, OPL3 starts counting immediately, unlike OPL2. */ + else + timer_on_auto(&dev->timers[tmr], (tmr == 1) ? 320.0 : 80.0); + } else { + nuked_log("Timer %i stopped\n", tmr); + if (tmr == 1) { + dev->status &= ~STAT_TMR2_OVER; + } else + dev->status &= ~STAT_TMR1_OVER; + } +} + +static void +nuked_timer_1(void *priv) +{ + nuked_drv_t *dev = (nuked_drv_t *) priv; + + nuked_timer_tick(dev, 0); +} + +static void +nuked_timer_2(void *priv) +{ + nuked_drv_t *dev = (nuked_drv_t *) priv; + + nuked_timer_tick(dev, 1); +} + +static void +nuked_drv_set_do_cycles(void *priv, int8_t do_cycles) +{ + nuked_drv_t *dev = (nuked_drv_t *)priv; + + if (do_cycles) + dev->flags |= FLAG_CYCLES; + else + dev->flags &= ~FLAG_CYCLES; +} + +static void * +nuked_drv_init(const device_t *info) +{ + nuked_drv_t *dev = (nuked_drv_t *) calloc(1, sizeof(nuked_drv_t)); + dev->flags = FLAG_CYCLES; + if (info->local == FM_YMF262) + dev->flags |= FLAG_OPL3; + else + dev->status = 0x06; + + /* Initialize the NukedOPL object. */ + nuked_init(&dev->opl, 48000); + + timer_add(&dev->timers[0], nuked_timer_1, dev, 0); + timer_add(&dev->timers[1], nuked_timer_2, dev, 0); + + return dev; +} + +static void +nuked_drv_close(void *priv) +{ + nuked_drv_t *dev = (nuked_drv_t *)priv; free(dev); } + +static int32_t * +nuked_drv_update(void *priv) +{ + nuked_drv_t *dev = (nuked_drv_t *)priv; + + if (dev->pos >= sound_pos_global) + return dev->buffer; + + nuked_generate_stream(&dev->opl, + &dev->buffer[dev->pos * 2], + sound_pos_global - dev->pos); + + for (; dev->pos < sound_pos_global; dev->pos++) { + dev->buffer[dev->pos * 2] /= 2; + dev->buffer[(dev->pos * 2) + 1] /= 2; + } + + return dev->buffer; +} + +static uint8_t +nuked_drv_read(uint16_t port, void *priv) +{ + nuked_drv_t *dev = (nuked_drv_t *) priv; + + if (dev->flags & FLAG_CYCLES) + cycles -= ((int) (isa_timing * 8)); + + nuked_drv_update(dev); + + uint8_t ret = 0xff; + + if ((port & 0x0003) == 0x0000) { + ret = dev->status; + if (dev->status & STAT_TMR_OVER) + ret |= STAT_TMR_ANY; + } + + nuked_log("OPL statret = %02x, status = %02x\n", ret, dev->status); + + return ret; +} + +static void +nuked_drv_write(uint16_t port, uint8_t val, void *priv) +{ + nuked_drv_t *dev = (nuked_drv_t *)priv; + nuked_drv_update(dev); + + if ((port & 0x0001) == 0x0001) { + nuked_write_reg_buffered(&dev->opl, dev->port, val); + + switch (dev->port) { + case 0x02: /* Timer 1 */ + dev->timer_count[0] = val; + nuked_log("Timer 0 count now: %i\n", dev->timer_count[0]); + break; + + case 0x03: /* Timer 2 */ + dev->timer_count[1] = val; + nuked_log("Timer 1 count now: %i\n", dev->timer_count[1]); + break; + + case 0x04: /* Timer control */ + if (val & CTRL_RESET) { + nuked_log("Resetting timer status...\n"); + dev->status &= ~STAT_TMR_OVER; + } else { + dev->timer_ctrl = val; + nuked_timer_control(dev, 0, val & CTRL_TMR1_START); + nuked_timer_control(dev, 1, val & CTRL_TMR2_START); + nuked_log("Status mask now %02X (val = %02X)\n", (val & ~CTRL_TMR_MASK) & CTRL_TMR_MASK, val); + } + break; + } + } else { + dev->port = nuked_write_addr(&dev->opl, port, val) & 0x01ff; + + if (!(dev->flags & FLAG_OPL3)) + dev->port &= 0x00ff; + } +} + +static void +nuked_drv_reset_buffer(void *priv) { + nuked_drv_t *dev = (nuked_drv_t *)priv; + + dev->pos = 0; +} + +const device_t ym3812_nuked_device = { + .name = "Yamaha YM3812 OPL2 (NUKED)", + .internal_name = "ym3812_nuked", + .flags = 0, + .local = FM_YM3812, + .init = nuked_drv_init, + .close = nuked_drv_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + +const device_t ymf262_nuked_device = { + .name = "Yamaha YMF262 OPL3 (NUKED)", + .internal_name = "ymf262_nuked", + .flags = 0, + .local = FM_YMF262, + .init = nuked_drv_init, + .close = nuked_drv_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + +const fm_drv_t nuked_opl_drv = { + &nuked_drv_read, + &nuked_drv_write, + &nuked_drv_update, + &nuked_drv_reset_buffer, + &nuked_drv_set_do_cycles, + NULL, +}; \ No newline at end of file diff --git a/src/sound/snd_opl_ymfm.cpp b/src/sound/snd_opl_ymfm.cpp new file mode 100644 index 000000000..6acbf79f3 --- /dev/null +++ b/src/sound/snd_opl_ymfm.cpp @@ -0,0 +1,362 @@ +/* + * 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 emulator. + * + * + * Authors: Adrien Moulin, + * + * Copyright 2022 Adrien Moulin. + */ + +#include +#include +#include +#include +#include "ymfm/ymfm_opl.h" + +extern "C" { +#include <86box/timer.h> +#include <86box/device.h> +#include <86box/sound.h> +#include <86box/snd_opl.h> +} + +#define RSM_FRAC 10 + +enum { + FLAG_CYCLES = (1 << 0) +}; + +class YMFMChipBase +{ +public: + YMFMChipBase(uint32_t clock, fm_type type, uint32_t samplerate) + : m_buf_pos(0), m_flags(0), m_type(type) + { + memset(m_buffer, 0, sizeof(m_buffer)); + } + + virtual ~YMFMChipBase() + { + } + + 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 void generate_resampled(int32_t *data, uint32_t num_samples) = 0; + virtual int32_t * update() = 0; + virtual uint8_t read(uint16_t addr) = 0; + +protected: + int32_t m_buffer[SOUNDBUFLEN * 2]; + uint32_t m_buf_pos; + int8_t m_flags; + fm_type m_type; +}; + +template +class YMFMChip : public YMFMChipBase, public ymfm::ymfm_interface +{ +public: + YMFMChip(uint32_t clock, fm_type type, uint32_t samplerate) + : YMFMChipBase(clock, type, samplerate) + , m_chip(*this) + , m_clock(clock) + , 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 / (double) m_clock; + + timer_add(&m_timers[0], YMFMChip::timer1, this, 0); + timer_add(&m_timers[1], YMFMChip::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; + + pc_timer_t *timer = &m_timers[tnum]; + if (duration_in_clocks < 0) { + timer_stop(timer); + } else { + double period = m_clock_us * duration_in_clocks; + timer_on_auto(timer, period); + } + } + + 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 (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]; + } + } + } + +virtual void generate_resampled(int32_t *data, uint32_t num_samples) override + { + 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 (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; + } + } + + virtual int32_t *update() override + { + if (m_buf_pos >= sound_pos_global) + return m_buffer; + + generate_resampled(&m_buffer[m_buf_pos * 2], sound_pos_global - m_buf_pos); + + for (; m_buf_pos < sound_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); + } + + static void timer1(void *priv) + { + YMFMChip *drv = (YMFMChip *) priv; + drv->m_engine->engine_timer_expired(0); + } + + static void timer2(void *priv) + { + YMFMChip *drv = (YMFMChip *) priv; + drv->m_engine->engine_timer_expired(1); + } + +private: + ChipType m_chip; + uint32_t m_clock; + double m_clock_us; + typename ChipType::output_data m_output; + pc_timer_t m_timers[2]; + + // 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> + +#ifdef ENABLE_OPL_LOG + +static void +ymfm_log(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); +} +#else +# define ymfm_log(fmt, ...) +#endif + +static void * +ymfm_drv_init(const device_t *info) +{ + YMFMChipBase *fm; + + switch (info->local) { + case FM_YM3812: + default: + fm = (YMFMChipBase *) new YMFMChip(3579545, FM_YM3812, 48000); + break; + + case FM_YMF262: + fm = (YMFMChipBase *) new YMFMChip(14318181, FM_YMF262, 48000); + break; + + case FM_YMF289B: + fm = (YMFMChipBase *) new YMFMChip(33868800, FM_YMF289B, 48000); + break; + } + + fm->set_do_cycles(1); + + return fm; +} + +static void +ymfm_drv_close(void *priv) +{ + YMFMChipBase *drv = (YMFMChipBase *) priv; + + if (drv != NULL) + delete(drv); +} + +static uint8_t +ymfm_drv_read(uint16_t port, void *priv) +{ + YMFMChipBase *drv = (YMFMChipBase *) priv; + + 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_drv_write(uint16_t port, uint8_t val, void *priv) +{ + YMFMChipBase *drv = (YMFMChipBase *) priv; + ymfm_log("YMFM write port %04x value = %02x\n", port, val); + drv->write(port, val); + drv->update(); +} + +static int32_t * +ymfm_drv_update(void *priv) { + YMFMChipBase *drv = (YMFMChipBase *) priv; + + return drv->update(); +} + +static void +ymfm_drv_reset_buffer(void *priv) { + YMFMChipBase *drv = (YMFMChipBase *) priv; + + drv->reset_buffer(); +} + +static void +ymfm_drv_set_do_cycles(void *priv, int8_t do_cycles) +{ + YMFMChipBase *drv = (YMFMChipBase *) priv; + drv->set_do_cycles(do_cycles); +} + +const device_t ym3812_ymfm_device = { + .name = "Yamaha YM3812 OPL2 (YMFM)", + .internal_name = "ym3812_ymfm", + .flags = 0, + .local = FM_YM3812, + .init = ymfm_drv_init, + .close = ymfm_drv_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + +const device_t ymf262_ymfm_device = { + .name = "Yamaha YMF262 OPL3 (YMFM)", + .internal_name = "ymf262_ymfm", + .flags = 0, + .local = FM_YMF262, + .init = ymfm_drv_init, + .close = ymfm_drv_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + +const device_t ymf289b_ymfm_device = { + .name = "Yamaha YMF289B OPL3-L (YMFM)", + .internal_name = "ymf289b_ymfm", + .flags = 0, + .local = FM_YMF289B, + .init = ymfm_drv_init, + .close = ymfm_drv_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + +const fm_drv_t ymfm_drv { + &ymfm_drv_read, + &ymfm_drv_write, + &ymfm_drv_update, + &ymfm_drv_reset_buffer, + &ymfm_drv_set_do_cycles, + NULL, +}; + +} diff --git a/src/sound/snd_pas16.c b/src/sound/snd_pas16.c index 33bd51b34..1662598c9 100644 --- a/src/sound/snd_pas16.c +++ b/src/sound/snd_pas16.c @@ -137,7 +137,7 @@ typedef struct pas16_t { int64_t enable[3]; } pit; - opl_t opl; + fm_drv_t opl; sb_dsp_t dsp; int16_t pcm_buffer[2][SOUNDBUFLEN]; @@ -201,7 +201,7 @@ pas16_in(uint16_t port, void *p) case 0x389: case 0x38a: case 0x38b: - temp = opl3_read((port - pas16->base) + 0x388, &pas16->opl); + temp = pas16->opl.read((port - pas16->base) + 0x388, pas16->opl.priv); break; case 0xb88: @@ -301,7 +301,7 @@ pas16_out(uint16_t port, uint8_t val, void *p) case 0x389: case 0x38a: case 0x38b: - opl3_write((port - pas16->base) + 0x388, val, &pas16->opl); + pas16->opl.write((port - pas16->base) + 0x388, val, pas16->opl.priv); break; case 0xb88: @@ -702,17 +702,17 @@ pas16_get_buffer(int32_t *buffer, int len, void *p) pas16_t *pas16 = (pas16_t *) p; int c; - opl3_update(&pas16->opl); + int32_t *opl_buf = pas16->opl.update(pas16->opl.priv); sb_dsp_update(&pas16->dsp); pas16_update(pas16); for (c = 0; c < len * 2; c++) { - buffer[c] += pas16->opl.buffer[c]; + buffer[c] += opl_buf[c]; buffer[c] += (int16_t) (sb_iir(0, c & 1, (double) pas16->dsp.buffer[c]) / 1.3) / 2; buffer[c] += (pas16->pcm_buffer[c & 1][c >> 1] / 2); } pas16->pos = 0; - pas16->opl.pos = 0; + pas16->opl.reset_buffer(pas16->opl.priv); pas16->dsp.pos = 0; } @@ -722,7 +722,7 @@ pas16_init(const device_t *info) pas16_t *pas16 = malloc(sizeof(pas16_t)); memset(pas16, 0, sizeof(pas16_t)); - opl3_init(&pas16->opl); + fm_driver_get(FM_YMF262, &pas16->opl); sb_dsp_init(&pas16->dsp, SB2, SB_SUBTYPE_DEFAULT, pas16); io_sethandler(0x9a01, 0x0001, NULL, NULL, NULL, pas16_out_base, NULL, NULL, pas16); diff --git a/src/sound/snd_sb.c b/src/sound/snd_sb.c index aaafd11fa..16fcfe9d3 100644 --- a/src/sound/snd_sb.c +++ b/src/sound/snd_sb.c @@ -183,9 +183,10 @@ sb_get_buffer_sb2(int32_t *buffer, int len, void *p) sb_ct1335_mixer_t *mixer = &sb->mixer_sb2; int c; double out_mono = 0.0, out_l = 0.0, out_r = 0.0; + int32_t *opl_buf = NULL; if (sb->opl_enabled) - opl2_update(&sb->opl); + opl_buf = sb->opl.update(sb->opl.priv); sb_dsp_update(&sb->dsp); @@ -198,7 +199,7 @@ sb_get_buffer_sb2(int32_t *buffer, int len, void *p) out_r = 0.0; if (sb->opl_enabled) - out_mono = ((double) sb->opl.buffer[c]) * 0.7171630859375; + out_mono = ((double) opl_buf[c]) * 0.7171630859375; if (sb->cms_enabled) { out_l += sb->cms.buffer[c]; @@ -234,7 +235,7 @@ sb_get_buffer_sb2(int32_t *buffer, int len, void *p) sb->pos = 0; if (sb->opl_enabled) - sb->opl.pos = 0; + sb->opl.reset_buffer(sb->opl.priv); sb->dsp.pos = 0; @@ -265,13 +266,14 @@ sb_get_buffer_sbpro(int32_t *buffer, int len, void *p) sb_ct1345_mixer_t *mixer = &sb->mixer_sbpro; int c; double out_l = 0.0, out_r = 0.0; + int32_t *opl_buf, *opl2_buf; if (sb->opl_enabled) { if (sb->dsp.sb_type == SBPRO) { - opl2_update(&sb->opl); - opl2_update(&sb->opl2); + opl_buf = sb->opl.update(sb->opl.priv); + opl2_buf = sb->opl2.update(sb->opl2.priv); } else - opl3_update(&sb->opl); + opl_buf = sb->opl.update(sb->opl.priv); } sb_dsp_update(&sb->dsp); @@ -283,11 +285,11 @@ sb_get_buffer_sbpro(int32_t *buffer, int len, void *p) if (sb->dsp.sb_type == SBPRO) { /* Two chips for LEFT and RIGHT channels. Each chip stores data into the LEFT channel only (no sample alternating.) */ - out_l = (((double) sb->opl.buffer[c]) * mixer->fm_l) * 0.7171630859375; - out_r = (((double) sb->opl2.buffer[c]) * mixer->fm_r) * 0.7171630859375; + out_l = (((double) opl_buf[c]) * mixer->fm_l) * 0.7171630859375; + out_r = (((double) opl2_buf[c]) * mixer->fm_r) * 0.7171630859375; } else { - out_l = (((double) sb->opl.buffer[c]) * mixer->fm_l) * 0.7171630859375; - out_r = (((double) sb->opl.buffer[c + 1]) * mixer->fm_r) * 0.7171630859375; + out_l = (((double) opl_buf[c]) * mixer->fm_l) * 0.7171630859375; + out_r = (((double) opl_buf[c + 1]) * mixer->fm_r) * 0.7171630859375; } } @@ -311,9 +313,9 @@ sb_get_buffer_sbpro(int32_t *buffer, int len, void *p) sb->pos = 0; if (sb->opl_enabled) { - sb->opl.pos = 0; + sb->opl.reset_buffer(sb->opl.priv); if (sb->dsp.sb_type != SBPRO) - sb->opl2.pos = 0; + sb->opl2.reset_buffer(sb->opl2.priv); } sb->dsp.pos = 0; @@ -345,9 +347,10 @@ sb_get_buffer_sb16_awe32(int32_t *buffer, int len, void *p) int32_t in_l, in_r; double out_l = 0.0, out_r = 0.0; double bass_treble; + int32_t *opl_buf = NULL; if (sb->opl_enabled) - opl3_update(&sb->opl); + opl_buf = sb->opl.update(sb->opl.priv); if (sb->dsp.sb_type > SB16) emu8k_update(&sb->emu8k); @@ -361,8 +364,8 @@ sb_get_buffer_sb16_awe32(int32_t *buffer, int len, void *p) c_emu8k = ((((c / 2) * 44100) / 48000) * 2); if (sb->opl_enabled) { - out_l = ((double) sb->opl.buffer[c]) * mixer->fm_l * 0.7171630859375; - out_r = ((double) sb->opl.buffer[c + 1]) * mixer->fm_r * 0.7171630859375; + out_l = ((double) opl_buf[c]) * mixer->fm_l * 0.7171630859375; + out_r = ((double) opl_buf[c + 1]) * mixer->fm_r * 0.7171630859375; } if (sb->dsp.sb_type > SB16) { @@ -456,7 +459,7 @@ sb_get_buffer_sb16_awe32(int32_t *buffer, int len, void *p) sb->pos = 0; if (sb->opl_enabled) - sb->opl.pos = 0; + sb->opl.reset_buffer(sb->opl.priv); sb->dsp.pos = 0; @@ -1088,13 +1091,13 @@ sb_mcv_write(int port, uint8_t val, void *p) addr = sb_mcv_addr[sb->pos_regs[4] & 7]; if (sb->opl_enabled) { io_removehandler(addr + 8, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_removehandler(0x0388, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); } /* DSP I/O handler is activated in sb_dsp_setaddr */ sb_dsp_setaddr(&sb->dsp, 0); @@ -1106,13 +1109,13 @@ sb_mcv_write(int port, uint8_t val, void *p) if (sb->opl_enabled) { io_sethandler(addr + 8, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(0x0388, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); } /* DSP I/O handler is activated in sb_dsp_setaddr */ sb_dsp_setaddr(&sb->dsp, addr); @@ -1152,17 +1155,17 @@ sb_pro_mcv_write(int port, uint8_t val, void *p) addr = (sb->pos_regs[2] & 0x20) ? 0x220 : 0x240; io_removehandler(addr, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_removehandler(addr + 8, 0x0002, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_removehandler(0x0388, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_removehandler(addr + 4, 0x0002, sb_ct1345_mixer_read, NULL, NULL, sb_ct1345_mixer_write, NULL, NULL, @@ -1176,16 +1179,16 @@ sb_pro_mcv_write(int port, uint8_t val, void *p) addr = (sb->pos_regs[2] & 0x20) ? 0x220 : 0x240; io_sethandler(addr, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(addr + 8, 0x0002, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(0x0388, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, sb->opl.priv); io_sethandler(addr + 4, 0x0002, sb_ct1345_mixer_read, NULL, NULL, sb_ct1345_mixer_write, NULL, NULL, @@ -1198,6 +1201,151 @@ sb_pro_mcv_write(int port, uint8_t val, void *p) sb_dsp_setdma8(&sb->dsp, sb->pos_regs[4] & 3); } +static uint8_t +sb_16_reply_mca_read(int port, void *p) +{ + sb_t *sb = (sb_t *) p; + uint8_t ret = sb->pos_regs[port & 7]; + + sb_log("sb_16_reply_mca_read: port=%04x ret=%02x\n", port, ret); + + return ret; +} + +static void +sb_16_reply_mca_write(int port, uint8_t val, void *p) +{ + uint16_t addr, mpu401_addr; + int low_dma, high_dma; + sb_t *sb = (sb_t *) p; + + if (port < 0x102) + return; + + sb_log("sb_16_reply_mca_write: port=%04x val=%02x\n", port, val); + + switch (sb->pos_regs[2] & 0xc4) { + case 4: + addr = 0x220; + break; + case 0x44: + addr = 0x240; + break; + case 0x84: + addr = 0x260; + break; + case 0xc4: + addr = 0x280; + break; + case 0: + default: + addr = 0; + break; + } + + if (addr) { + io_removehandler(addr, 0x0004, + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); + io_removehandler(addr + 8, 0x0002, + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); + io_removehandler(0x0388, 0x0004, + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); + io_removehandler(addr + 4, 0x0002, + sb_ct1745_mixer_read, NULL, NULL, + sb_ct1745_mixer_write, NULL, NULL, + sb); + } + + /* DSP I/O handler is activated in sb_dsp_setaddr */ + sb_dsp_setaddr(&sb->dsp, 0); + mpu401_change_addr(sb->mpu, 0); + gameport_remap(sb->gameport, 0); + + sb->pos_regs[port & 7] = val; + + if (sb->pos_regs[2] & 1) { + switch (sb->pos_regs[2] & 0xc4) { + case 4: + addr = 0x220; + break; + case 0x44: + addr = 0x240; + break; + case 0x84: + addr = 0x260; + break; + case 0xc4: + addr = 0x280; + break; + case 0: + default: + addr = 0; + break; + } + switch (sb->pos_regs[2] & 0x18) { + case 8: + mpu401_addr = 0x330; + break; + case 0x18: + mpu401_addr = 0x300; + break; + case 0: + default: + mpu401_addr = 0; + break; + } + + if (addr) { + io_sethandler(addr, 0x0004, + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); + io_sethandler(addr + 8, 0x0002, + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); + io_sethandler(0x0388, 0x0004, + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, sb->opl.priv); + io_sethandler(addr + 4, 0x0002, + sb_ct1745_mixer_read, NULL, NULL, + sb_ct1745_mixer_write, NULL, NULL, + sb); + } + + /* DSP I/O handler is activated in sb_dsp_setaddr */ + sb_dsp_setaddr(&sb->dsp, addr); + mpu401_change_addr(sb->mpu, mpu401_addr); + gameport_remap(sb->gameport, (sb->pos_regs[2] & 0x20) ? 0x200 : 0); + } + + switch (sb->pos_regs[4] & 0x60) { + case 0x20: + sb_dsp_setirq(&sb->dsp, 5); + break; + case 0x40: + sb_dsp_setirq(&sb->dsp, 7); + break; + case 0x60: + sb_dsp_setirq(&sb->dsp, 10); + break; + } + + low_dma = sb->pos_regs[3] & 3; + high_dma = (sb->pos_regs[3] >> 4) & 7; + if (!high_dma) + high_dma = low_dma; + + sb_dsp_setdma8(&sb->dsp, low_dma); + sb_dsp_setdma16(&sb->dsp, high_dma); +} + static void sb_16_pnp_config_changed(uint8_t ld, isapnp_device_config_t *config, void *priv) { @@ -1208,13 +1356,13 @@ sb_16_pnp_config_changed(uint8_t ld, isapnp_device_config_t *config, void *priv) switch (ld) { case 0: /* Audio */ io_removehandler(addr, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_removehandler(addr + 8, 0x0002, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_removehandler(addr + 4, 0x0002, sb_ct1745_mixer_read, NULL, NULL, sb_ct1745_mixer_write, NULL, NULL, @@ -1224,9 +1372,9 @@ sb_16_pnp_config_changed(uint8_t ld, isapnp_device_config_t *config, void *priv) if (addr) { sb->opl_pnp_addr = 0; io_removehandler(addr, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); } sb_dsp_setaddr(&sb->dsp, 0); @@ -1240,13 +1388,13 @@ sb_16_pnp_config_changed(uint8_t ld, isapnp_device_config_t *config, void *priv) addr = config->io[0].base; if (addr != ISAPNP_IO_DISABLED) { io_sethandler(addr, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(addr + 8, 0x0002, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(addr + 4, 0x0002, sb_ct1745_mixer_read, NULL, NULL, sb_ct1745_mixer_write, NULL, NULL, @@ -1263,9 +1411,9 @@ sb_16_pnp_config_changed(uint8_t ld, isapnp_device_config_t *config, void *priv) if (addr != ISAPNP_IO_DISABLED) { sb->opl_pnp_addr = addr; io_sethandler(addr, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); } val = config->irq[0].irq; @@ -1349,7 +1497,7 @@ sb_1_init(const device_t *info) sb->opl_enabled = device_get_config_int("opl"); if (sb->opl_enabled) - opl2_init(&sb->opl); + fm_driver_get(FM_YM3812, &sb->opl); sb_dsp_init(&sb->dsp, SB1, SB_SUBTYPE_DEFAULT, sb); sb_dsp_setaddr(&sb->dsp, addr); @@ -1358,13 +1506,13 @@ sb_1_init(const device_t *info) /* DSP I/O handler is activated in sb_dsp_setaddr */ if (sb->opl_enabled) { io_sethandler(addr + 8, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(0x0388, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); } sb->cms_enabled = 1; @@ -1397,7 +1545,7 @@ sb_15_init(const device_t *info) sb->opl_enabled = device_get_config_int("opl"); if (sb->opl_enabled) - opl2_init(&sb->opl); + fm_driver_get(FM_YM3812, &sb->opl); sb_dsp_init(&sb->dsp, SB15, SB_SUBTYPE_DEFAULT, sb); sb_dsp_setaddr(&sb->dsp, addr); @@ -1406,13 +1554,13 @@ sb_15_init(const device_t *info) /* DSP I/O handler is activated in sb_dsp_setaddr */ if (sb->opl_enabled) { io_sethandler(addr + 8, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(0x0388, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); } sb->cms_enabled = device_get_config_int("cms"); @@ -1445,7 +1593,7 @@ sb_mcv_init(const device_t *info) sb->opl_enabled = device_get_config_int("opl"); if (sb->opl_enabled) - opl2_init(&sb->opl); + fm_driver_get(FM_YM3812, &sb->opl); sb_dsp_init(&sb->dsp, SB15, SB_SUBTYPE_DEFAULT, sb); sb_dsp_setaddr(&sb->dsp, 0); @@ -1490,7 +1638,7 @@ sb_2_init(const device_t *info) sb->opl_enabled = device_get_config_int("opl"); if (sb->opl_enabled) - opl2_init(&sb->opl); + fm_driver_get(FM_YM3812, &sb->opl); sb_dsp_init(&sb->dsp, SB2, SB_SUBTYPE_DEFAULT, sb); sb_dsp_setaddr(&sb->dsp, addr); @@ -1504,18 +1652,18 @@ sb_2_init(const device_t *info) if (sb->opl_enabled) { if (!sb->cms_enabled) { io_sethandler(addr, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.write); } io_sethandler(addr + 8, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.write); io_sethandler(0x0388, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.write); } if (sb->cms_enabled) { @@ -1550,8 +1698,8 @@ sb_pro_v1_opl_read(uint16_t port, void *priv) cycles -= ((int) (isa_timing * 8)); - (void) opl2_read(port, &sb->opl2); // read, but ignore - return (opl2_read(port, &sb->opl)); + (void) sb->opl2.read(port, sb->opl2.priv); // read, but ignore + return (sb->opl.read(port, sb->opl.priv)); } static void @@ -1559,8 +1707,8 @@ sb_pro_v1_opl_write(uint16_t port, uint8_t val, void *priv) { sb_t *sb = (sb_t *) priv; - opl2_write(port, val, &sb->opl); - opl2_write(port, val, &sb->opl2); + sb->opl.write(port, val, sb->opl.priv); + sb->opl2.write(port, val, sb->opl2.priv); } static void * @@ -1578,10 +1726,10 @@ sb_pro_v1_init(const device_t *info) sb->opl_enabled = device_get_config_int("opl"); if (sb->opl_enabled) { - opl2_init(&sb->opl); - opl_set_do_cycles(&sb->opl, 0); - opl2_init(&sb->opl2); - opl_set_do_cycles(&sb->opl2, 0); + fm_driver_get(FM_YM3812, &sb->opl); + sb->opl.set_do_cycles(sb->opl.priv, 0); + fm_driver_get(FM_YM3812, &sb->opl2); + sb->opl2.set_do_cycles(sb->opl2.priv, 0); } sb_dsp_init(&sb->dsp, SBPRO, SB_SUBTYPE_DEFAULT, sb); @@ -1592,13 +1740,13 @@ sb_pro_v1_init(const device_t *info) /* DSP I/O handler is activated in sb_dsp_setaddr */ if (sb->opl_enabled) { io_sethandler(addr, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(addr + 2, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl2); + sb->opl2.read, NULL, NULL, + sb->opl2.write, NULL, NULL, + sb->opl2.priv); io_sethandler(addr + 8, 0x0002, sb_pro_v1_opl_read, NULL, NULL, sb_pro_v1_opl_write, NULL, NULL, @@ -1638,7 +1786,7 @@ sb_pro_v2_init(const device_t *info) sb->opl_enabled = device_get_config_int("opl"); if (sb->opl_enabled) - opl3_init(&sb->opl); + fm_driver_get(FM_YMF262, &sb->opl); sb_dsp_init(&sb->dsp, SBPRO2, SB_SUBTYPE_DEFAULT, sb); sb_dsp_setaddr(&sb->dsp, addr); @@ -1648,17 +1796,17 @@ sb_pro_v2_init(const device_t *info) /* DSP I/O handler is activated in sb_dsp_setaddr */ if (sb->opl_enabled) { io_sethandler(addr, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(addr + 8, 0x0002, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(0x0388, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); } sb->mixer_enabled = 1; @@ -1687,7 +1835,7 @@ sb_pro_mcv_init(const device_t *info) memset(sb, 0, sizeof(sb_t)); sb->opl_enabled = 1; - opl3_init(&sb->opl); + fm_driver_get(FM_YMF262, &sb->opl); sb_dsp_init(&sb->dsp, SBPRO2, SB_SUBTYPE_DEFAULT, sb); sb_ct1345_mixer_reset(sb); @@ -1713,7 +1861,7 @@ sb_pro_compat_init(const device_t *info) sb_t *sb = malloc(sizeof(sb_t)); memset(sb, 0, sizeof(sb_t)); - opl3_init(&sb->opl); + fm_driver_get(FM_YMF262, &sb->opl); sb_dsp_init(&sb->dsp, SBPRO2, SB_SUBTYPE_DEFAULT, sb); sb_ct1345_mixer_reset(sb); @@ -1740,7 +1888,7 @@ sb_16_init(const device_t *info) sb->opl_enabled = device_get_config_int("opl"); if (sb->opl_enabled) - opl3_init(&sb->opl); + fm_driver_get(FM_YMF262, &sb->opl); sb_dsp_init(&sb->dsp, SB16, SB_SUBTYPE_DEFAULT, sb); sb_dsp_setaddr(&sb->dsp, addr); @@ -1751,17 +1899,17 @@ sb_16_init(const device_t *info) if (sb->opl_enabled) { io_sethandler(addr, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(addr + 8, 0x0002, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(0x0388, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); } sb->mixer_enabled = 1; @@ -1785,6 +1933,41 @@ sb_16_init(const device_t *info) return sb; } +static void * +sb_16_reply_mca_init(const device_t *info) +{ + sb_t *sb = malloc(sizeof(sb_t)); + memset(sb, 0x00, sizeof(sb_t)); + + sb->opl_enabled = 1; + fm_driver_get(FM_YMF262, &sb->opl); + + sb_dsp_init(&sb->dsp, SB16, SB_SUBTYPE_DEFAULT, sb); + sb_ct1745_mixer_reset(sb); + + sb->mixer_enabled = 1; + sb->mixer_sb16.output_filter = 1; + sound_add_handler(sb_get_buffer_sb16_awe32, sb); + sound_set_cd_audio_filter(sb16_awe32_filter_cd_audio, sb); + + sb->mpu = (mpu_t *) malloc(sizeof(mpu_t)); + memset(sb->mpu, 0, sizeof(mpu_t)); + mpu401_init(sb->mpu, 0, 0, M_UART, device_get_config_int("receive_input401")); + sb_dsp_set_mpu(&sb->dsp, sb->mpu); + + if (device_get_config_int("receive_input")) + midi_in_handler(1, sb_dsp_input_msg, sb_dsp_input_sysex, &sb->dsp); + + sb->gameport = gameport_add(&gameport_device); + + /* I/O handlers activated in sb_pro_mcv_write */ + mca_add(sb_16_reply_mca_read, sb_16_reply_mca_write, sb_mcv_feedb, NULL, sb); + sb->pos_regs[0] = 0x38; + sb->pos_regs[1] = 0x51; + + return sb; +} + static void * sb_16_pnp_init(const device_t *info) { @@ -1792,7 +1975,7 @@ sb_16_pnp_init(const device_t *info) memset(sb, 0x00, sizeof(sb_t)); sb->opl_enabled = 1; - opl3_init(&sb->opl); + fm_driver_get(FM_YMF262, &sb->opl); sb_dsp_init(&sb->dsp, SB16, SB_SUBTYPE_DEFAULT, sb); sb_ct1745_mixer_reset(sb); @@ -1835,7 +2018,7 @@ sb_16_compat_init(const device_t *info) sb_t *sb = malloc(sizeof(sb_t)); memset(sb, 0, sizeof(sb_t)); - opl3_init(&sb->opl); + fm_driver_get(FM_YMF262, &sb->opl); sb_dsp_init(&sb->dsp, SB16, SB_SUBTYPE_DEFAULT, sb); sb_ct1745_mixer_reset(sb); @@ -1900,7 +2083,7 @@ sb_awe32_init(const device_t *info) sb->opl_enabled = device_get_config_int("opl"); if (sb->opl_enabled) - opl3_init(&sb->opl); + fm_driver_get(FM_YMF262, &sb->opl); sb_dsp_init(&sb->dsp, SBAWE32, SB_SUBTYPE_DEFAULT, sb); sb_dsp_setaddr(&sb->dsp, addr); @@ -1911,17 +2094,17 @@ sb_awe32_init(const device_t *info) if (sb->opl_enabled) { io_sethandler(addr, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(addr + 8, 0x0002, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(0x0388, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); } sb->mixer_enabled = 1; @@ -1956,7 +2139,7 @@ sb_awe32_pnp_init(const device_t *info) memset(sb, 0x00, sizeof(sb_t)); sb->opl_enabled = 1; - opl3_init(&sb->opl); + fm_driver_get(FM_YMF262, &sb->opl); sb_dsp_init(&sb->dsp, ((info->local == 2) || (info->local == 3) || (info->local == 4)) ? SBAWE64 : SBAWE32, SB_SUBTYPE_DEFAULT, sb); sb_ct1745_mixer_reset(sb); @@ -3371,6 +3554,20 @@ const device_t sb_16_device = { .config = sb_16_config }; +const device_t sb_16_reply_mca_device = { + .name = "Sound Blaster 16 Reply MCA", + .internal_name = "sb16_reply_mca", + .flags = DEVICE_MCA, + .local = 0, + .init = sb_16_reply_mca_init, + .close = sb_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = sb_speed_changed, + .force_redraw = NULL, + .config = sb_16_pnp_config +}; + const device_t sb_16_pnp_device = { .name = "Sound Blaster 16 PnP", .internal_name = "sb16_pnp", diff --git a/src/sound/snd_sb_dsp.c b/src/sound/snd_sb_dsp.c index 3cbb223f3..c87f45e2f 100644 --- a/src/sound/snd_sb_dsp.c +++ b/src/sound/snd_sb_dsp.c @@ -326,14 +326,14 @@ void sb_dsp_speed_changed(sb_dsp_t *dsp) { if (dsp->sb_timeo < 256) - dsp->sblatcho = (256.0 - (double) dsp->sb_timeo); + dsp->sblatcho = TIMER_USEC * (256 - dsp->sb_timeo); else - dsp->sblatcho = ((1000000.0 / ((double) dsp->sb_timeo - 256.0))); + dsp->sblatcho = (uint64_t) (TIMER_USEC * (1000000.0f / (float) (dsp->sb_timeo - 256))); if (dsp->sb_timei < 256) - dsp->sblatchi = (256.0 - (double) dsp->sb_timei); + dsp->sblatchi = TIMER_USEC * (256 - dsp->sb_timei); else - dsp->sblatchi = ((1000000.0 / ((double) dsp->sb_timei - 256.0))); + dsp->sblatchi = (uint64_t) (TIMER_USEC * (1000000.0f / (float) (dsp->sb_timei - 256))); } void @@ -359,7 +359,7 @@ sb_start_dma(sb_dsp_t *dsp, int dma8, int autoinit, uint8_t format, int len) dsp->sb_16_enable = 0; dsp->sb_8_output = 1; if (!timer_is_enabled(&dsp->output_timer)) - timer_on_auto(&dsp->output_timer, dsp->sblatcho); + timer_set_delay_u64(&dsp->output_timer, dsp->sblatcho); dsp->sbleftright = dsp->sbleftright_default; dsp->sbdacpos = 0; } else { @@ -372,7 +372,7 @@ sb_start_dma(sb_dsp_t *dsp, int dma8, int autoinit, uint8_t format, int len) dsp->sb_8_enable = 0; dsp->sb_16_output = 1; if (!timer_is_enabled(&dsp->output_timer)) - timer_on_auto(&dsp->output_timer, dsp->sblatcho); + timer_set_delay_u64(&dsp->output_timer, dsp->sblatcho); } } @@ -389,7 +389,7 @@ sb_start_dma_i(sb_dsp_t *dsp, int dma8, int autoinit, uint8_t format, int len) dsp->sb_16_enable = 0; dsp->sb_8_output = 0; if (!timer_is_enabled(&dsp->input_timer)) - timer_on_auto(&dsp->input_timer, dsp->sblatchi); + timer_set_delay_u64(&dsp->input_timer, dsp->sblatchi); } else { dsp->sb_16_length = dsp->sb_16_origlength = len; dsp->sb_16_format = format; @@ -400,7 +400,7 @@ sb_start_dma_i(sb_dsp_t *dsp, int dma8, int autoinit, uint8_t format, int len) dsp->sb_8_enable = 0; dsp->sb_16_output = 0; if (!timer_is_enabled(&dsp->input_timer)) - timer_on_auto(&dsp->input_timer, dsp->sblatchi); + timer_set_delay_u64(&dsp->input_timer, dsp->sblatchi); } memset(dsp->record_buffer, 0, sizeof(dsp->record_buffer)); @@ -508,10 +508,10 @@ sb_exec_command(sb_dsp_t *dsp) mode does not imply such samplerate. Position is increased in sb_poll_i(). */ if (!timer_is_enabled(&dsp->input_timer)) { dsp->sb_timei = 256 - 22; - dsp->sblatchi = 22.0; + dsp->sblatchi = TIMER_USEC * 22; temp = 1000000 / 22; dsp->sb_freq = temp; - timer_on_auto(&dsp->input_timer, dsp->sblatchi); + timer_set_delay_u64(&dsp->input_timer, dsp->sblatchi); } break; case 0x24: /* 8-bit single cycle DMA input */ @@ -561,7 +561,7 @@ sb_exec_command(sb_dsp_t *dsp) break; case 0x40: /* Set time constant */ dsp->sb_timei = dsp->sb_timeo = dsp->sb_data[0]; - dsp->sblatcho = dsp->sblatchi = (256.0 - (double) dsp->sb_data[0]); + dsp->sblatcho = dsp->sblatchi = TIMER_USEC * (256 - dsp->sb_data[0]); temp = 256 - dsp->sb_data[0]; temp = 1000000 / temp; sb_dsp_log("Sample rate - %ihz (%i)\n", temp, dsp->sblatcho); @@ -572,8 +572,8 @@ sb_exec_command(sb_dsp_t *dsp) case 0x41: /* Set output sampling rate */ case 0x42: /* Set input sampling rate */ if (dsp->sb_type >= SB16) { - dsp->sblatcho = ((1000000.0 / (double) (dsp->sb_data[1] + (dsp->sb_data[0] << 8)))); - sb_dsp_log("Sample rate - %ihz (%lf)\n", dsp->sb_data[1] + (dsp->sb_data[0] << 8), dsp->sblatcho); + dsp->sblatcho = (uint64_t) (TIMER_USEC * (1000000.0f / (float) (dsp->sb_data[1] + (dsp->sb_data[0] << 8)))); + sb_dsp_log("Sample rate - %ihz (%i)\n", dsp->sb_data[1] + (dsp->sb_data[0] << 8), dsp->sblatcho); temp = dsp->sb_freq; dsp->sb_freq = dsp->sb_data[1] + (dsp->sb_data[0] << 8); dsp->sb_timeo = 256LL + dsp->sb_freq; @@ -631,7 +631,7 @@ sb_exec_command(sb_dsp_t *dsp) case 0x80: /* Pause DAC */ dsp->sb_pausetime = dsp->sb_data[0] + (dsp->sb_data[1] << 8); if (!timer_is_enabled(&dsp->output_timer)) - timer_on_auto(&dsp->output_timer, dsp->sblatcho); + timer_set_delay_u64(&dsp->output_timer, dsp->sblatcho); break; case 0x90: /* High speed 8-bit autoinit DMA output */ if (dsp->sb_type >= SB2) @@ -1200,7 +1200,7 @@ pollsb(void *p) int tempi, ref; int data[2]; - timer_on_auto(&dsp->output_timer, dsp->sblatcho); + timer_advance_u64(&dsp->output_timer, dsp->sblatcho); if (dsp->sb_8_enable && !dsp->sb_8_pause && dsp->sb_pausetime < 0 && dsp->sb_8_output) { sb_dsp_update(dsp); @@ -1457,7 +1457,7 @@ sb_poll_i(void *p) sb_dsp_t *dsp = (sb_dsp_t *) p; int processed = 0; - timer_on_auto(&dsp->input_timer, dsp->sblatchi); + timer_advance_u64(&dsp->input_timer, dsp->sblatchi); if (dsp->sb_8_enable && !dsp->sb_8_pause && dsp->sb_pausetime < 0 && !dsp->sb_8_output) { switch (dsp->sb_8_format) { diff --git a/src/sound/snd_wss.c b/src/sound/snd_wss.c index 8df4791c6..b4859e128 100644 --- a/src/sound/snd_wss.c +++ b/src/sound/snd_wss.c @@ -52,7 +52,7 @@ typedef struct wss_t { uint8_t config; ad1848_t ad1848; - opl_t opl; + fm_drv_t opl; int opl_enabled; uint8_t pos_regs[8]; @@ -81,14 +81,14 @@ wss_get_buffer(int32_t *buffer, int len, void *priv) wss_t *wss = (wss_t *) priv; int c; - opl3_update(&wss->opl); + int32_t *opl_buf = wss->opl.update(wss->opl.priv); ad1848_update(&wss->ad1848); for (c = 0; c < len * 2; c++) { - buffer[c] += wss->opl.buffer[c]; + buffer[c] += opl_buf[c]; buffer[c] += wss->ad1848.buffer[c] / 2; } - wss->opl.pos = 0; + wss->opl.reset_buffer(wss->opl.priv); wss->ad1848.pos = 0; } @@ -102,7 +102,7 @@ wss_init(const device_t *info) wss->opl_enabled = device_get_config_int("opl"); if (wss->opl_enabled) - opl3_init(&wss->opl); + fm_driver_get(FM_YMF262, &wss->opl); ad1848_init(&wss->ad1848, AD1848_TYPE_DEFAULT); @@ -111,9 +111,9 @@ wss_init(const device_t *info) if (wss->opl_enabled) io_sethandler(0x0388, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &wss->opl); + wss->opl.read, NULL, NULL, + wss->opl.write, NULL, NULL, + wss->opl.priv); io_sethandler(addr, 0x0004, wss_read, NULL, NULL, @@ -150,9 +150,9 @@ ncr_audio_mca_write(int port, uint8_t val, void *priv) addr = ports[(wss->pos_regs[2] & 0x18) >> 3]; io_removehandler(0x0388, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &wss->opl); + wss->opl.read, NULL, NULL, + wss->opl.write, NULL, NULL, + wss->opl.priv); io_removehandler(addr, 0x0004, wss_read, NULL, NULL, wss_write, NULL, NULL, @@ -169,9 +169,9 @@ ncr_audio_mca_write(int port, uint8_t val, void *priv) if (wss->opl_enabled) io_sethandler(0x0388, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &wss->opl); + wss->opl.read, NULL, NULL, + wss->opl.write, NULL, NULL, + wss->opl.priv); io_sethandler(addr, 0x0004, wss_read, NULL, NULL, @@ -197,7 +197,7 @@ ncr_audio_init(const device_t *info) wss_t *wss = malloc(sizeof(wss_t)); memset(wss, 0, sizeof(wss_t)); - opl3_init(&wss->opl); + fm_driver_get(FM_YMF262, &wss->opl); ad1848_init(&wss->ad1848, AD1848_TYPE_DEFAULT); ad1848_setirq(&wss->ad1848, 7); diff --git a/src/sound/sound.c b/src/sound/sound.c index ec9e51f91..604dac38f 100644 --- a/src/sound/sound.c +++ b/src/sound/sound.c @@ -140,6 +140,7 @@ static const SOUND_CARD sound_cards[] = { { &ncr_business_audio_device }, { &sb_mcv_device }, { &sb_pro_mcv_device }, + { &sb_16_reply_mca_device }, { &cmi8338_device }, { &cmi8738_device }, { &es1371_device }, diff --git a/src/sound/ymfm/CMakeLists.txt b/src/sound/ymfm/CMakeLists.txt new file mode 100644 index 000000000..2041719ae --- /dev/null +++ b/src/sound/ymfm/CMakeLists.txt @@ -0,0 +1 @@ +add_library(ymfm STATIC ymfm_misc.cpp ymfm_opl.cpp ymfm_opm.cpp ymfm_opn.cpp ymfm_opq.cpp ymfm_opz.cpp ymfm_pcm.cpp ymfm_adpcm.cpp) \ No newline at end of file diff --git a/src/sound/ymfm/ymfm.h b/src/sound/ymfm/ymfm.h new file mode 100644 index 000000000..d9c3bca3b --- /dev/null +++ b/src/sound/ymfm/ymfm.h @@ -0,0 +1,571 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_H +#define YMFM_H + +#pragma once + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) + #define _CRT_SECURE_NO_WARNINGS +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ymfm +{ + +//********************************************************* +// DEBUGGING +//********************************************************* + +class debug +{ +public: + // masks to help isolate specific channels + static constexpr uint32_t GLOBAL_FM_CHANNEL_MASK = 0xffffffff; + static constexpr uint32_t GLOBAL_ADPCM_A_CHANNEL_MASK = 0xffffffff; + static constexpr uint32_t GLOBAL_ADPCM_B_CHANNEL_MASK = 0xffffffff; + static constexpr uint32_t GLOBAL_PCM_CHANNEL_MASK = 0xffffffff; + + // types of logging + static constexpr bool LOG_FM_WRITES = false; + static constexpr bool LOG_KEYON_EVENTS = false; + static constexpr bool LOG_UNEXPECTED_READ_WRITES = false; + + // helpers to write based on the log type + template static void log_fm_write(Params &&... args) { if (LOG_FM_WRITES) log(args...); } + template static void log_keyon(Params &&... args) { if (LOG_KEYON_EVENTS) log(args...); } + template static void log_unexpected_read_write(Params &&... args) { if (LOG_UNEXPECTED_READ_WRITES) log(args...); } + + // downstream helper to output log data; defaults to printf + template static void log(Params &&... args) { printf(args...); } +}; + + + +//********************************************************* +// GLOBAL HELPERS +//********************************************************* + +//------------------------------------------------- +// bitfield - extract a bitfield from the given +// value, starting at bit 'start' for a length of +// 'length' bits +//------------------------------------------------- + +inline uint32_t bitfield(uint32_t value, int start, int length = 1) +{ + return (value >> start) & ((1 << length) - 1); +} + + +//------------------------------------------------- +// clamp - clamp between the minimum and maximum +// values provided +//------------------------------------------------- + +inline int32_t clamp(int32_t value, int32_t minval, int32_t maxval) +{ + if (value < minval) + return minval; + if (value > maxval) + return maxval; + return value; +} + + +//------------------------------------------------- +// 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 +// versions for various architectures are included +// below +//------------------------------------------------- + +#if defined(__GNUC__) + +inline uint8_t count_leading_zeros(uint32_t value) +{ + if (value == 0) + return 32; + return __builtin_clz(value); +} + +#elif defined(_MSC_VER) + +inline uint8_t count_leading_zeros(uint32_t value) +{ + unsigned long index; + return _BitScanReverse(&index, value) ? uint8_t(31U - index) : 32U; +} + +#else + +inline uint8_t count_leading_zeros(uint32_t value) +{ + if (value == 0) + return 32; + uint8_t count; + for (count = 0; int32_t(value) >= 0; count++) + value <<= 1; + return count; +} + +#endif + + +// Many of the Yamaha FM chips emit a floating-point value, which is sent to +// a DAC for processing. The exact format of this floating-point value is +// documented below. This description only makes sense if the "internal" +// format treats sign as 1=positive and 0=negative, so the helpers below +// presume that. +// +// Internal OPx data 16-bit signed data Exp Sign Mantissa +// ================= ================= === ==== ======== +// 1 1xxxxxxxx------ -> 0 1xxxxxxxx------ -> 111 1 1xxxxxxx +// 1 01xxxxxxxx----- -> 0 01xxxxxxxx----- -> 110 1 1xxxxxxx +// 1 001xxxxxxxx---- -> 0 001xxxxxxxx---- -> 101 1 1xxxxxxx +// 1 0001xxxxxxxx--- -> 0 0001xxxxxxxx--- -> 100 1 1xxxxxxx +// 1 00001xxxxxxxx-- -> 0 00001xxxxxxxx-- -> 011 1 1xxxxxxx +// 1 000001xxxxxxxx- -> 0 000001xxxxxxxx- -> 010 1 1xxxxxxx +// 1 000000xxxxxxxxx -> 0 000000xxxxxxxxx -> 001 1 xxxxxxxx +// 0 111111xxxxxxxxx -> 1 111111xxxxxxxxx -> 001 0 xxxxxxxx +// 0 111110xxxxxxxx- -> 1 111110xxxxxxxx- -> 010 0 0xxxxxxx +// 0 11110xxxxxxxx-- -> 1 11110xxxxxxxx-- -> 011 0 0xxxxxxx +// 0 1110xxxxxxxx--- -> 1 1110xxxxxxxx--- -> 100 0 0xxxxxxx +// 0 110xxxxxxxx---- -> 1 110xxxxxxxx---- -> 101 0 0xxxxxxx +// 0 10xxxxxxxx----- -> 1 10xxxxxxxx----- -> 110 0 0xxxxxxx +// 0 0xxxxxxxx------ -> 1 0xxxxxxxx------ -> 111 0 0xxxxxxx + +//------------------------------------------------- +// encode_fp - given a 32-bit signed input value +// convert it to a signed 3.10 floating-point +// value +//------------------------------------------------- + +inline int16_t encode_fp(int32_t value) +{ + // handle overflows first + if (value < -32768) + return (7 << 10) | 0x000; + if (value > 32767) + return (7 << 10) | 0x3ff; + + // we need to count the number of leading sign bits after the sign + // we can use count_leading_zeros if we invert negative values + int32_t scanvalue = value ^ (int32_t(value) >> 31); + + // exponent is related to the number of leading bits starting from bit 14 + int exponent = 7 - count_leading_zeros(scanvalue << 17); + + // smallest exponent value allowed is 1 + exponent = std::max(exponent, 1); + + // mantissa + int32_t mantissa = value >> (exponent - 1); + + // assemble into final form, inverting the sign + return ((exponent << 10) | (mantissa & 0x3ff)) ^ 0x200; +} + + +//------------------------------------------------- +// decode_fp - given a 3.10 floating-point value, +// convert it to a signed 16-bit value +//------------------------------------------------- + +inline int16_t decode_fp(int16_t value) +{ + // invert the sign and the exponent + value ^= 0x1e00; + + // shift mantissa up to 16 bits then apply inverted exponent + return int16_t(value << 6) >> bitfield(value, 10, 3); +} + + +//------------------------------------------------- +// roundtrip_fp - compute the result of a round +// trip through the encode/decode process above +//------------------------------------------------- + +inline int16_t roundtrip_fp(int32_t value) +{ + // handle overflows first + if (value < -32768) + return -32768; + if (value > 32767) + return 32767; + + // we need to count the number of leading sign bits after the sign + // we can use count_leading_zeros if we invert negative values + int32_t scanvalue = value ^ (int32_t(value) >> 31); + + // exponent is related to the number of leading bits starting from bit 14 + int exponent = 7 - count_leading_zeros(scanvalue << 17); + + // smallest exponent value allowed is 1 + exponent = std::max(exponent, 1); + + // apply the shift back and forth to zero out bits that are lost + exponent -= 1; + return (value >> exponent) << exponent; +} + + + +//********************************************************* +// HELPER CLASSES +//********************************************************* + +// various envelope states +enum envelope_state : uint32_t +{ + EG_DEPRESS = 0, // OPLL only; set EG_HAS_DEPRESS to enable + EG_ATTACK = 1, + EG_DECAY = 2, + EG_SUSTAIN = 3, + EG_RELEASE = 4, + EG_REVERB = 5, // OPQ/OPZ only; set EG_HAS_REVERB to enable + EG_STATES = 6 +}; + +// external I/O access classes +enum access_class : uint32_t +{ + ACCESS_IO = 0, + ACCESS_ADPCM_A, + ACCESS_ADPCM_B, + ACCESS_PCM, + ACCESS_CLASSES +}; + + + +//********************************************************* +// HELPER CLASSES +//********************************************************* + +// ======================> ymfm_output + +// struct containing an array of output values +template +struct ymfm_output +{ + // clear all outputs to 0 + ymfm_output &clear() + { + for (uint32_t index = 0; index < NumOutputs; index++) + data[index] = 0; + return *this; + } + + // clamp all outputs to a 16-bit signed value + ymfm_output &clamp16() + { + for (uint32_t index = 0; index < NumOutputs; index++) + data[index] = clamp(data[index], -32768, 32767); + return *this; + } + + // run each output value through the floating-point processor + ymfm_output &roundtrip_fp() + { + for (uint32_t index = 0; index < NumOutputs; index++) + data[index] = ymfm::roundtrip_fp(data[index]); + return *this; + } + + // internal state + int32_t data[NumOutputs]; +}; + + +// ======================> ymfm_wavfile + +// this class is a debugging helper that accumulates data and writes it to wav files +template +class ymfm_wavfile +{ +public: + // construction + ymfm_wavfile(uint32_t samplerate = 44100) : + m_samplerate(samplerate) + { + } + + // configuration + ymfm_wavfile &set_index(uint32_t index) { m_index = index; return *this; } + ymfm_wavfile &set_samplerate(uint32_t samplerate) { m_samplerate = samplerate; return *this; } + + // destruction + ~ymfm_wavfile() + { + if (!m_buffer.empty()) + { + // create file + char name[20]; + sprintf(name, "wavlog-%02d.wav", m_index); + FILE *out = fopen(name, "wb"); + + // make the wav file header + uint8_t header[44]; + memcpy(&header[0], "RIFF", 4); + *(uint32_t *)&header[4] = m_buffer.size() * 2 + 44 - 8; + memcpy(&header[8], "WAVE", 4); + memcpy(&header[12], "fmt ", 4); + *(uint32_t *)&header[16] = 16; + *(uint16_t *)&header[20] = 1; + *(uint16_t *)&header[22] = _Channels; + *(uint32_t *)&header[24] = m_samplerate; + *(uint32_t *)&header[28] = m_samplerate * 2 * _Channels; + *(uint16_t *)&header[32] = 2 * _Channels; + *(uint16_t *)&header[34] = 16; + memcpy(&header[36], "data", 4); + *(uint32_t *)&header[40] = m_buffer.size() * 2 + 44 - 44; + + // write header then data + fwrite(&header[0], 1, sizeof(header), out); + fwrite(&m_buffer[0], 2, m_buffer.size(), out); + fclose(out); + } + } + + // add data to the file + template + void add(ymfm_output<_Outputs> output) + { + int16_t sum[_Channels] = { 0 }; + for (int index = 0; index < _Outputs; index++) + sum[index % _Channels] += output.data[index]; + for (int index = 0; index < _Channels; index++) + m_buffer.push_back(sum[index]); + } + + // add data to the file, using a reference + template + void add(ymfm_output<_Outputs> output, ymfm_output<_Outputs> const &ref) + { + int16_t sum[_Channels] = { 0 }; + for (int index = 0; index < _Outputs; index++) + sum[index % _Channels] += output.data[index] - ref.data[index]; + for (int index = 0; index < _Channels; index++) + m_buffer.push_back(sum[index]); + } + +private: + // internal state + uint32_t m_index; + uint32_t m_samplerate; + std::vector m_buffer; +}; + + +// ======================> ymfm_saved_state + +// this class contains a managed vector of bytes that is used to save and +// restore state +class ymfm_saved_state +{ +public: + // construction + ymfm_saved_state(std::vector &buffer, bool saving) : + m_buffer(buffer), + m_offset(saving ? -1 : 0) + { + if (saving) + buffer.resize(0); + } + + // are we saving or restoring? + bool saving() const { return (m_offset < 0); } + + // generic save/restore + template + void save_restore(DataType &data) + { + if (saving()) + save(data); + else + restore(data); + } + +public: + // save data to the buffer + void save(bool &data) { write(data ? 1 : 0); } + void save(int8_t &data) { write(data); } + void save(uint8_t &data) { write(data); } + void save(int16_t &data) { write(uint8_t(data)).write(data >> 8); } + void save(uint16_t &data) { write(uint8_t(data)).write(data >> 8); } + void save(int32_t &data) { write(data).write(data >> 8).write(data >> 16).write(data >> 24); } + void save(uint32_t &data) { write(data).write(data >> 8).write(data >> 16).write(data >> 24); } + void save(envelope_state &data) { write(uint8_t(data)); } + template + void save(DataType (&data)[Count]) { for (uint32_t index = 0; index < Count; index++) save(data[index]); } + + // restore data from the buffer + void restore(bool &data) { data = read() ? true : false; } + void restore(int8_t &data) { data = read(); } + void restore(uint8_t &data) { data = read(); } + void restore(int16_t &data) { data = read(); data |= read() << 8; } + void restore(uint16_t &data) { data = read(); data |= read() << 8; } + void restore(int32_t &data) { data = read(); data |= read() << 8; data |= read() << 16; data |= read() << 24; } + void restore(uint32_t &data) { data = read(); data |= read() << 8; data |= read() << 16; data |= read() << 24; } + void restore(envelope_state &data) { data = envelope_state(read()); } + template + void restore(DataType (&data)[Count]) { for (uint32_t index = 0; index < Count; index++) restore(data[index]); } + + // internal helper + ymfm_saved_state &write(uint8_t data) { m_buffer.push_back(data); return *this; } + uint8_t read() { return (m_offset < int32_t(m_buffer.size())) ? m_buffer[m_offset++] : 0; } + + // internal state + std::vector &m_buffer; + int32_t m_offset; +}; + + + +//********************************************************* +// INTERFACE CLASSES +//********************************************************* + +// ======================> ymfm_engine_callbacks + +// this class represents functions in the engine that the ymfm_interface +// needs to be able to call; it is represented here as a separate interface +// that is independent of the actual engine implementation +class ymfm_engine_callbacks +{ +public: + // timer callback; called by the interface when a timer fires + virtual void engine_timer_expired(uint32_t tnum) = 0; + + // check interrupts; called by the interface after synchronization + virtual void engine_check_interrupts() = 0; + + // mode register write; called by the interface after synchronization + virtual void engine_mode_write(uint8_t data) = 0; +}; + + +// ======================> ymfm_interface + +// this class represents the interface between the fm_engine and the outside +// world; it provides hooks for timers, synchronization, and I/O +class ymfm_interface +{ + // the engine is our friend + template friend class fm_engine_base; + +public: + // 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 + // all prefixed with ymfm_ to reduce the likelihood of namespace collisions + + // + // timing and synchronizaton + // + + // the chip implementation calls this when a write happens to the mode + // register, which could affect timers and interrupts; our responsibility + // is to ensure the system is up to date before calling the engine's + // engine_mode_write() method + virtual void ymfm_sync_mode_write(uint8_t data) { m_engine->engine_mode_write(data); } + + // the chip implementation calls this when the chip's status has changed, + // which may affect the interrupt state; our responsibility is to ensure + // the system is up to date before calling the engine's + // engine_check_interrupts() method + virtual void ymfm_sync_check_interrupts() { m_engine->engine_check_interrupts(); } + + // the chip implementation calls this when one of the two internal timers + // has changed state; our responsibility is to arrange to call the engine's + // engine_timer_expired() method after the provided number of clocks; if + // duration_in_clocks is negative, we should cancel any outstanding timers + virtual void ymfm_set_timer(uint32_t tnum, int32_t duration_in_clocks) { } + + // the chip implementation calls this to indicate that the chip should be + // considered in a busy state until the given number of clocks has passed; + // our responsibility is to compute and remember the ending time based on + // the chip's clock for later checking + virtual void ymfm_set_busy_end(uint32_t clocks) { } + + // the chip implementation calls this to see if the chip is still currently + // is a busy state, as specified by a previous call to ymfm_set_busy_end(); + // our responsibility is to compare the current time against the previously + // noted busy end time and return true if we haven't yet passed it + virtual bool ymfm_is_busy() { return false; } + + // + // I/O functions + // + + // the chip implementation calls this when the state of the IRQ signal has + // changed due to a status change; our responsibility is to respond as + // needed to the change in IRQ state, signaling any consumers + virtual void ymfm_update_irq(bool asserted) { } + + // the chip implementation calls this whenever data is read from outside + // of the chip; our responsibility is to provide the data requested + virtual uint8_t ymfm_external_read(access_class type, uint32_t address) { return 0; } + + // the chip implementation calls this whenever data is written outside + // of the chip; our responsibility is to pass the written data on to any consumers + virtual void ymfm_external_write(access_class type, uint32_t address, uint8_t data) { } + +protected: + // pointer to engine callbacks -- this is set directly by the engine at + // construction time + ymfm_engine_callbacks *m_engine; +}; + +} + +#endif // YMFM_H diff --git a/src/sound/ymfm/ymfm_adpcm.cpp b/src/sound/ymfm/ymfm_adpcm.cpp new file mode 100644 index 000000000..4bc22beb2 --- /dev/null +++ b/src/sound/ymfm/ymfm_adpcm.cpp @@ -0,0 +1,807 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "ymfm_adpcm.h" + +namespace ymfm +{ + +//********************************************************* +// ADPCM "A" REGISTERS +//********************************************************* + +//------------------------------------------------- +// reset - reset the register state +//------------------------------------------------- + +void adpcm_a_registers::reset() +{ + std::fill_n(&m_regdata[0], REGISTERS, 0); + + // initialize the pans to on by default, and max instrument volume; + // some neogeo homebrews (for example ffeast) rely on this + m_regdata[0x08] = m_regdata[0x09] = m_regdata[0x0a] = + m_regdata[0x0b] = m_regdata[0x0c] = m_regdata[0x0d] = 0xdf; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void adpcm_a_registers::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_regdata); +} + + +//********************************************************* +// ADPCM "A" CHANNEL +//********************************************************* + +//------------------------------------------------- +// adpcm_a_channel - constructor +//------------------------------------------------- + +adpcm_a_channel::adpcm_a_channel(adpcm_a_engine &owner, uint32_t choffs, uint32_t addrshift) : + m_choffs(choffs), + m_address_shift(addrshift), + m_playing(0), + m_curnibble(0), + m_curbyte(0), + m_curaddress(0), + m_accumulator(0), + m_step_index(0), + m_regs(owner.regs()), + m_owner(owner) +{ +} + + +//------------------------------------------------- +// reset - reset the channel state +//------------------------------------------------- + +void adpcm_a_channel::reset() +{ + m_playing = 0; + m_curnibble = 0; + m_curbyte = 0; + m_curaddress = 0; + m_accumulator = 0; + m_step_index = 0; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void adpcm_a_channel::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_playing); + state.save_restore(m_curnibble); + state.save_restore(m_curbyte); + state.save_restore(m_curaddress); + state.save_restore(m_accumulator); + state.save_restore(m_step_index); +} + + +//------------------------------------------------- +// keyonoff - signal key on/off +//------------------------------------------------- + +void adpcm_a_channel::keyonoff(bool on) +{ + // QUESTION: repeated key ons restart the sample? + m_playing = on; + if (m_playing) + { + m_curaddress = m_regs.ch_start(m_choffs) << m_address_shift; + m_curnibble = 0; + m_curbyte = 0; + m_accumulator = 0; + m_step_index = 0; + + // don't log masked channels + if (((debug::GLOBAL_ADPCM_A_CHANNEL_MASK >> m_choffs) & 1) != 0) + debug::log_keyon("KeyOn ADPCM-A%d: pan=%d%d start=%04X end=%04X level=%02X\n", + m_choffs, + m_regs.ch_pan_left(m_choffs), + m_regs.ch_pan_right(m_choffs), + m_regs.ch_start(m_choffs), + m_regs.ch_end(m_choffs), + m_regs.ch_instrument_level(m_choffs)); + } +} + + +//------------------------------------------------- +// clock - master clocking function +//------------------------------------------------- + +bool adpcm_a_channel::clock() +{ + // if not playing, just output 0 + if (m_playing == 0) + { + m_accumulator = 0; + return false; + } + + // if we're about to read nibble 0, fetch the data + uint8_t data; + if (m_curnibble == 0) + { + // stop when we hit the end address; apparently only low 20 bits are used for + // comparison on the YM2610: this affects sample playback in some games, for + // example twinspri character select screen music will skip some samples if + // this is not correct + // + // note also: end address is inclusive, so wait until we are about to fetch + // the sample just after the end before stopping; this is needed for nitd's + // jump sound, for example + uint32_t end = (m_regs.ch_end(m_choffs) + 1) << m_address_shift; + if (((m_curaddress ^ end) & 0xfffff) == 0) + { + m_playing = m_accumulator = 0; + return true; + } + + m_curbyte = m_owner.intf().ymfm_external_read(ACCESS_ADPCM_A, m_curaddress++); + data = m_curbyte >> 4; + m_curnibble = 1; + } + + // otherwise just extract from the previosuly-fetched byte + else + { + data = m_curbyte & 0xf; + m_curnibble = 0; + } + + // compute the ADPCM delta + static uint16_t const s_steps[49] = + { + 16, 17, 19, 21, 23, 25, 28, + 31, 34, 37, 41, 45, 50, 55, + 60, 66, 73, 80, 88, 97, 107, + 118, 130, 143, 157, 173, 190, 209, + 230, 253, 279, 307, 337, 371, 408, + 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552 + }; + int32_t delta = (2 * bitfield(data, 0, 3) + 1) * s_steps[m_step_index] / 8; + if (bitfield(data, 3)) + delta = -delta; + + // the 12-bit accumulator wraps on the ym2610 and ym2608 (like the msm5205) + m_accumulator = (m_accumulator + delta) & 0xfff; + + // adjust ADPCM step + static int8_t const s_step_inc[8] = { -1, -1, -1, -1, 2, 5, 7, 9 }; + m_step_index = clamp(m_step_index + s_step_inc[bitfield(data, 0, 3)], 0, 48); + + return false; +} + + +//------------------------------------------------- +// output - return the computed output value, with +// panning applied +//------------------------------------------------- + +template +void adpcm_a_channel::output(ymfm_output &output) const +{ + // volume combines instrument and total levels + int vol = (m_regs.ch_instrument_level(m_choffs) ^ 0x1f) + (m_regs.total_level() ^ 0x3f); + + // if combined is maximum, don't add to outputs + if (vol >= 63) + return; + + // convert into a shift and a multiplier + // QUESTION: verify this from other sources + int8_t mul = 15 - (vol & 7); + uint8_t shift = 4 + 1 + (vol >> 3); + + // m_accumulator is a 12-bit value; shift up to sign-extend; + // the downshift is incorporated into 'shift' + int16_t value = ((int16_t(m_accumulator << 4) * mul) >> shift) & ~3; + + // apply to left/right as appropriate + if (NumOutputs == 1 || m_regs.ch_pan_left(m_choffs)) + output.data[0] += value; + if (NumOutputs > 1 && m_regs.ch_pan_right(m_choffs)) + output.data[1] += value; +} + + + +//********************************************************* +// ADPCM "A" ENGINE +//********************************************************* + +//------------------------------------------------- +// adpcm_a_engine - constructor +//------------------------------------------------- + +adpcm_a_engine::adpcm_a_engine(ymfm_interface &intf, uint32_t addrshift) : + m_intf(intf) +{ + // create the channels + for (int chnum = 0; chnum < CHANNELS; chnum++) + m_channel[chnum] = std::make_unique(*this, chnum, addrshift); +} + + +//------------------------------------------------- +// reset - reset the engine state +//------------------------------------------------- + +void adpcm_a_engine::reset() +{ + // reset register state + m_regs.reset(); + + // reset each channel + for (auto &chan : m_channel) + chan->reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void adpcm_a_engine::save_restore(ymfm_saved_state &state) +{ + // save register state + m_regs.save_restore(state); + + // save channel state + for (int chnum = 0; chnum < CHANNELS; chnum++) + m_channel[chnum]->save_restore(state); +} + + +//------------------------------------------------- +// clock - master clocking function +//------------------------------------------------- + +uint32_t adpcm_a_engine::clock(uint32_t chanmask) +{ + // clock each channel, setting a bit in result if it finished + uint32_t result = 0; + for (int chnum = 0; chnum < CHANNELS; chnum++) + if (bitfield(chanmask, chnum)) + if (m_channel[chnum]->clock()) + result |= 1 << chnum; + + // return the bitmask of completed samples + return result; +} + + +//------------------------------------------------- +// update - master update function +//------------------------------------------------- + +template +void adpcm_a_engine::output(ymfm_output &output, uint32_t chanmask) +{ + // mask out some channels for debug purposes + chanmask &= debug::GLOBAL_ADPCM_A_CHANNEL_MASK; + + // compute the output of each channel + for (int chnum = 0; chnum < CHANNELS; chnum++) + if (bitfield(chanmask, chnum)) + m_channel[chnum]->output(output); +} + +template void adpcm_a_engine::output<1>(ymfm_output<1> &output, uint32_t chanmask); +template void adpcm_a_engine::output<2>(ymfm_output<2> &output, uint32_t chanmask); + + +//------------------------------------------------- +// write - handle writes to the ADPCM-A registers +//------------------------------------------------- + +void adpcm_a_engine::write(uint32_t regnum, uint8_t data) +{ + // store the raw value to the register array; + // most writes are passive, consumed only when needed + m_regs.write(regnum, data); + + // actively handle writes to the control register + if (regnum == 0x00) + for (int chnum = 0; chnum < CHANNELS; chnum++) + if (bitfield(data, chnum)) + m_channel[chnum]->keyonoff(bitfield(~data, 7)); +} + + + +//********************************************************* +// ADPCM "B" REGISTERS +//********************************************************* + +//------------------------------------------------- +// reset - reset the register state +//------------------------------------------------- + +void adpcm_b_registers::reset() +{ + std::fill_n(&m_regdata[0], REGISTERS, 0); + + // default limit to wide open + m_regdata[0x0c] = m_regdata[0x0d] = 0xff; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void adpcm_b_registers::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_regdata); +} + + + +//********************************************************* +// ADPCM "B" CHANNEL +//********************************************************* + +//------------------------------------------------- +// adpcm_b_channel - constructor +//------------------------------------------------- + +adpcm_b_channel::adpcm_b_channel(adpcm_b_engine &owner, uint32_t addrshift) : + m_address_shift(addrshift), + m_status(STATUS_BRDY), + m_curnibble(0), + m_curbyte(0), + m_dummy_read(0), + m_position(0), + m_curaddress(0), + m_accumulator(0), + m_prev_accum(0), + m_adpcm_step(STEP_MIN), + m_regs(owner.regs()), + m_owner(owner) +{ +} + + +//------------------------------------------------- +// reset - reset the channel state +//------------------------------------------------- + +void adpcm_b_channel::reset() +{ + m_status = STATUS_BRDY; + m_curnibble = 0; + m_curbyte = 0; + m_dummy_read = 0; + m_position = 0; + m_curaddress = 0; + m_accumulator = 0; + m_prev_accum = 0; + m_adpcm_step = STEP_MIN; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void adpcm_b_channel::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_status); + state.save_restore(m_curnibble); + state.save_restore(m_curbyte); + state.save_restore(m_dummy_read); + state.save_restore(m_position); + state.save_restore(m_curaddress); + state.save_restore(m_accumulator); + state.save_restore(m_prev_accum); + state.save_restore(m_adpcm_step); +} + + +//------------------------------------------------- +// clock - master clocking function +//------------------------------------------------- + +void adpcm_b_channel::clock() +{ + // only process if active and not recording (which we don't support) + if (!m_regs.execute() || m_regs.record() || (m_status & STATUS_PLAYING) == 0) + { + m_status &= ~STATUS_PLAYING; + return; + } + + // otherwise, advance the step + uint32_t position = m_position + m_regs.delta_n(); + m_position = uint16_t(position); + if (position < 0x10000) + return; + + // if we're about to process nibble 0, fetch sample + if (m_curnibble == 0) + { + // playing from RAM/ROM + if (m_regs.external()) + m_curbyte = m_owner.intf().ymfm_external_read(ACCESS_ADPCM_B, m_curaddress); + } + + // extract the nibble from our current byte + uint8_t data = uint8_t(m_curbyte << (4 * m_curnibble)) >> 4; + m_curnibble ^= 1; + + // we just processed the last nibble + if (m_curnibble == 0) + { + // if playing from RAM/ROM, check the end/limit address or advance + if (m_regs.external()) + { + // handle the sample end, either repeating or stopping + if (at_end()) + { + // if repeating, go back to the start + if (m_regs.repeat()) + load_start(); + + // otherwise, done; set the EOS bit + else + { + m_accumulator = 0; + m_prev_accum = 0; + m_status = (m_status & ~STATUS_PLAYING) | STATUS_EOS; + debug::log_keyon("%s\n", "ADPCM EOS"); + return; + } + } + + // wrap at the limit address + else if (at_limit()) + m_curaddress = 0; + + // otherwise, advance the current address + else + { + m_curaddress++; + m_curaddress &= 0xffffff; + } + } + + // if CPU-driven, copy the next byte and request more + else + { + m_curbyte = m_regs.cpudata(); + m_status |= STATUS_BRDY; + } + } + + // remember previous value for interpolation + m_prev_accum = m_accumulator; + + // forecast to next forecast: 1/8, 3/8, 5/8, 7/8, 9/8, 11/8, 13/8, 15/8 + int32_t delta = (2 * bitfield(data, 0, 3) + 1) * m_adpcm_step / 8; + if (bitfield(data, 3)) + delta = -delta; + + // add and clamp to 16 bits + m_accumulator = clamp(m_accumulator + delta, -32768, 32767); + + // scale the ADPCM step: 0.9, 0.9, 0.9, 0.9, 1.2, 1.6, 2.0, 2.4 + static uint8_t const s_step_scale[8] = { 57, 57, 57, 57, 77, 102, 128, 153 }; + m_adpcm_step = clamp((m_adpcm_step * s_step_scale[bitfield(data, 0, 3)]) / 64, STEP_MIN, STEP_MAX); +} + + +//------------------------------------------------- +// output - return the computed output value, with +// panning applied +//------------------------------------------------- + +template +void adpcm_b_channel::output(ymfm_output &output, uint32_t rshift) const +{ + // mask out some channels for debug purposes + if ((debug::GLOBAL_ADPCM_B_CHANNEL_MASK & 1) == 0) + return; + + // do a linear interpolation between samples + int32_t result = (m_prev_accum * int32_t((m_position ^ 0xffff) + 1) + m_accumulator * int32_t(m_position)) >> 16; + + // apply volume (level) in a linear fashion and reduce + result = (result * int32_t(m_regs.level())) >> (8 + rshift); + + // apply to left/right + if (NumOutputs == 1 || m_regs.pan_left()) + output.data[0] += result; + if (NumOutputs > 1 && m_regs.pan_right()) + output.data[1] += result; +} + + +//------------------------------------------------- +// read - handle special register reads +//------------------------------------------------- + +uint8_t adpcm_b_channel::read(uint32_t regnum) +{ + uint8_t result = 0; + + // register 8 reads over the bus under some conditions + if (regnum == 0x08 && !m_regs.execute() && !m_regs.record() && m_regs.external()) + { + // two dummy reads are consumed first + if (m_dummy_read != 0) + { + load_start(); + m_dummy_read--; + } + + // read the data + else + { + // read from outside of the chip + result = m_owner.intf().ymfm_external_read(ACCESS_ADPCM_B, m_curaddress++); + + // did we hit the end? if so, signal EOS + if (at_end()) + { + m_status = STATUS_EOS | STATUS_BRDY; + debug::log_keyon("%s\n", "ADPCM EOS"); + } + else + { + // signal ready + m_status = STATUS_BRDY; + } + + // wrap at the limit address + if (at_limit()) + m_curaddress = 0; + } + } + return result; +} + + +//------------------------------------------------- +// write - handle special register writes +//------------------------------------------------- + +void adpcm_b_channel::write(uint32_t regnum, uint8_t value) +{ + // register 0 can do a reset; also use writes here to reset the + // dummy read counter + if (regnum == 0x00) + { + if (m_regs.execute()) + { + load_start(); + + // don't log masked channels + if ((debug::GLOBAL_ADPCM_B_CHANNEL_MASK & 1) != 0) + debug::log_keyon("KeyOn ADPCM-B: rep=%d spk=%d pan=%d%d dac=%d 8b=%d rom=%d ext=%d rec=%d start=%04X end=%04X pre=%04X dn=%04X lvl=%02X lim=%04X\n", + m_regs.repeat(), + m_regs.speaker(), + m_regs.pan_left(), + m_regs.pan_right(), + m_regs.dac_enable(), + m_regs.dram_8bit(), + m_regs.rom_ram(), + m_regs.external(), + m_regs.record(), + m_regs.start(), + m_regs.end(), + m_regs.prescale(), + m_regs.delta_n(), + m_regs.level(), + m_regs.limit()); + } + else + m_status &= ~STATUS_EOS; + if (m_regs.resetflag()) + reset(); + if (m_regs.external()) + m_dummy_read = 2; + } + + // register 8 writes over the bus under some conditions + else if (regnum == 0x08) + { + // if writing from the CPU during execute, clear the ready flag + if (m_regs.execute() && !m_regs.record() && !m_regs.external()) + m_status &= ~STATUS_BRDY; + + // if writing during "record", pass through as data + else if (!m_regs.execute() && m_regs.record() && m_regs.external()) + { + // clear out dummy reads and set start address + if (m_dummy_read != 0) + { + load_start(); + m_dummy_read = 0; + } + + // did we hit the end? if so, signal EOS + if (at_end()) + { + debug::log_keyon("%s\n", "ADPCM EOS"); + m_status = STATUS_EOS | STATUS_BRDY; + } + + // otherwise, write the data and signal ready + else + { + m_owner.intf().ymfm_external_write(ACCESS_ADPCM_B, m_curaddress++, value); + m_status = STATUS_BRDY; + } + } + } +} + + +//------------------------------------------------- +// address_shift - compute the current address +// shift amount based on register settings +//------------------------------------------------- + +uint32_t adpcm_b_channel::address_shift() const +{ + // if a constant address shift, just provide that + if (m_address_shift != 0) + return m_address_shift; + + // if ROM or 8-bit DRAM, shift is 5 bits + if (m_regs.rom_ram()) + return 5; + if (m_regs.dram_8bit()) + return 5; + + // otherwise, shift is 2 bits + return 2; +} + + +//------------------------------------------------- +// load_start - load the start address and +// initialize the state +//------------------------------------------------- + +void adpcm_b_channel::load_start() +{ + m_status = (m_status & ~STATUS_EOS) | STATUS_PLAYING; + m_curaddress = m_regs.external() ? (m_regs.start() << address_shift()) : 0; + m_curnibble = 0; + m_curbyte = 0; + m_position = 0; + m_accumulator = 0; + m_prev_accum = 0; + m_adpcm_step = STEP_MIN; +} + + + +//********************************************************* +// ADPCM "B" ENGINE +//********************************************************* + +//------------------------------------------------- +// adpcm_b_engine - constructor +//------------------------------------------------- + +adpcm_b_engine::adpcm_b_engine(ymfm_interface &intf, uint32_t addrshift) : + m_intf(intf) +{ + // create the channel (only one supported for now, but leaving possibilities open) + m_channel = std::make_unique(*this, addrshift); +} + + +//------------------------------------------------- +// reset - reset the engine state +//------------------------------------------------- + +void adpcm_b_engine::reset() +{ + // reset registers + m_regs.reset(); + + // reset each channel + m_channel->reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void adpcm_b_engine::save_restore(ymfm_saved_state &state) +{ + // save our state + m_regs.save_restore(state); + + // save channel state + m_channel->save_restore(state); +} + + +//------------------------------------------------- +// clock - master clocking function +//------------------------------------------------- + +void adpcm_b_engine::clock() +{ + // clock each channel, setting a bit in result if it finished + m_channel->clock(); +} + + +//------------------------------------------------- +// output - master output function +//------------------------------------------------- + +template +void adpcm_b_engine::output(ymfm_output &output, uint32_t rshift) +{ + // compute the output of each channel + m_channel->output(output, rshift); +} + +template void adpcm_b_engine::output<1>(ymfm_output<1> &output, uint32_t rshift); +template void adpcm_b_engine::output<2>(ymfm_output<2> &output, uint32_t rshift); + + +//------------------------------------------------- +// write - handle writes to the ADPCM-B registers +//------------------------------------------------- + +void adpcm_b_engine::write(uint32_t regnum, uint8_t data) +{ + // store the raw value to the register array; + // most writes are passive, consumed only when needed + m_regs.write(regnum, data); + + // let the channel handle any special writes + m_channel->write(regnum, data); +} + +} diff --git a/src/sound/ymfm/ymfm_adpcm.h b/src/sound/ymfm/ymfm_adpcm.h new file mode 100644 index 000000000..d74e24f27 --- /dev/null +++ b/src/sound/ymfm/ymfm_adpcm.h @@ -0,0 +1,411 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_ADPCM_H +#define YMFM_ADPCM_H + +#pragma once + +#include "ymfm.h" + +namespace ymfm +{ + +//********************************************************* +// INTERFACE CLASSES +//********************************************************* + +// forward declarations +class adpcm_a_engine; +class adpcm_b_engine; + + +// ======================> adpcm_a_registers + +// +// ADPCM-A register map: +// +// System-wide registers: +// 00 x------- Dump (disable=1) or keyon (0) control +// --xxxxxx Mask of channels to dump or keyon +// 01 --xxxxxx Total level +// 02 xxxxxxxx Test register +// 08-0D x------- Pan left +// -x------ Pan right +// ---xxxxx Instrument level +// 10-15 xxxxxxxx Start address (low) +// 18-1D xxxxxxxx Start address (high) +// 20-25 xxxxxxxx End address (low) +// 28-2D xxxxxxxx End address (high) +// +class adpcm_a_registers +{ +public: + // constants + static constexpr uint32_t OUTPUTS = 2; + static constexpr uint32_t CHANNELS = 6; + static constexpr uint32_t REGISTERS = 0x30; + static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1; + + // constructor + adpcm_a_registers() { } + + // reset to initial state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // map channel number to register offset + static constexpr uint32_t channel_offset(uint32_t chnum) + { + assert(chnum < CHANNELS); + return chnum; + } + + // direct read/write access + void write(uint32_t index, uint8_t data) { m_regdata[index] = data; } + + // system-wide registers + uint32_t dump() const { return bitfield(m_regdata[0x00], 7); } + uint32_t dump_mask() const { return bitfield(m_regdata[0x00], 0, 6); } + uint32_t total_level() const { return bitfield(m_regdata[0x01], 0, 6); } + uint32_t test() const { return m_regdata[0x02]; } + + // per-channel registers + uint32_t ch_pan_left(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 7); } + uint32_t ch_pan_right(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 6); } + uint32_t ch_instrument_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 0, 5); } + uint32_t ch_start(uint32_t choffs) const { return m_regdata[choffs + 0x10] | (m_regdata[choffs + 0x18] << 8); } + uint32_t ch_end(uint32_t choffs) const { return m_regdata[choffs + 0x20] | (m_regdata[choffs + 0x28] << 8); } + + // per-channel writes + void write_start(uint32_t choffs, uint32_t address) + { + write(choffs + 0x10, address); + write(choffs + 0x18, address >> 8); + } + void write_end(uint32_t choffs, uint32_t address) + { + write(choffs + 0x20, address); + write(choffs + 0x28, address >> 8); + } + +private: + // internal state + uint8_t m_regdata[REGISTERS]; // register data +}; + + +// ======================> adpcm_a_channel + +class adpcm_a_channel +{ +public: + // constructor + adpcm_a_channel(adpcm_a_engine &owner, uint32_t choffs, uint32_t addrshift); + + // reset the channel state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // signal key on/off + void keyonoff(bool on); + + // master clockingfunction + bool clock(); + + // return the computed output value, with panning applied + template + void output(ymfm_output &output) const; + +private: + // internal state + uint32_t const m_choffs; // channel offset + uint32_t const m_address_shift; // address bits shift-left + uint32_t m_playing; // currently playing? + uint32_t m_curnibble; // index of the current nibble + uint32_t m_curbyte; // current byte of data + uint32_t m_curaddress; // current address + int32_t m_accumulator; // accumulator + int32_t m_step_index; // index in the stepping table + adpcm_a_registers &m_regs; // reference to registers + adpcm_a_engine &m_owner; // reference to our owner +}; + + +// ======================> adpcm_a_engine + +class adpcm_a_engine +{ +public: + static constexpr int CHANNELS = adpcm_a_registers::CHANNELS; + + // constructor + adpcm_a_engine(ymfm_interface &intf, uint32_t addrshift); + + // reset our status + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // master clocking function + uint32_t clock(uint32_t chanmask); + + // compute sum of channel outputs + template + void output(ymfm_output &output, uint32_t chanmask); + + // write to the ADPCM-A registers + void write(uint32_t regnum, uint8_t data); + + // set the start/end address for a channel (for hardcoded YM2608 percussion) + void set_start_end(uint8_t chnum, uint16_t start, uint16_t end) + { + uint32_t choffs = adpcm_a_registers::channel_offset(chnum); + m_regs.write_start(choffs, start); + m_regs.write_end(choffs, end); + } + + // return a reference to our interface + ymfm_interface &intf() { return m_intf; } + + // return a reference to our registers + adpcm_a_registers ®s() { return m_regs; } + +private: + // internal state + ymfm_interface &m_intf; // reference to the interface + std::unique_ptr m_channel[CHANNELS]; // array of channels + adpcm_a_registers m_regs; // registers +}; + + +// ======================> adpcm_b_registers + +// +// ADPCM-B register map: +// +// System-wide registers: +// 00 x------- Start of synthesis/analysis +// -x------ Record +// --x----- External/manual driving +// ---x---- Repeat playback +// ----x--- Speaker off +// -------x Reset +// 01 x------- Pan left +// -x------ Pan right +// ----x--- Start conversion +// -----x-- DAC enable +// ------x- DRAM access (1=8-bit granularity; 0=1-bit) +// -------x RAM/ROM (1=ROM, 0=RAM) +// 02 xxxxxxxx Start address (low) +// 03 xxxxxxxx Start address (high) +// 04 xxxxxxxx End address (low) +// 05 xxxxxxxx End address (high) +// 06 xxxxxxxx Prescale value (low) +// 07 -----xxx Prescale value (high) +// 08 xxxxxxxx CPU data/buffer +// 09 xxxxxxxx Delta-N frequency scale (low) +// 0a xxxxxxxx Delta-N frequency scale (high) +// 0b xxxxxxxx Level control +// 0c xxxxxxxx Limit address (low) +// 0d xxxxxxxx Limit address (high) +// 0e xxxxxxxx DAC data [YM2608/10] +// 0f xxxxxxxx PCM data [YM2608/10] +// 0e xxxxxxxx DAC data high [Y8950] +// 0f xx------ DAC data low [Y8950] +// 10 -----xxx DAC data exponent [Y8950] +// +class adpcm_b_registers +{ +public: + // constants + static constexpr uint32_t REGISTERS = 0x11; + + // constructor + adpcm_b_registers() { } + + // reset to initial state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // direct read/write access + void write(uint32_t index, uint8_t data) { m_regdata[index] = data; } + + // system-wide registers + uint32_t execute() const { return bitfield(m_regdata[0x00], 7); } + uint32_t record() const { return bitfield(m_regdata[0x00], 6); } + uint32_t external() const { return bitfield(m_regdata[0x00], 5); } + uint32_t repeat() const { return bitfield(m_regdata[0x00], 4); } + uint32_t speaker() const { return bitfield(m_regdata[0x00], 3); } + uint32_t resetflag() const { return bitfield(m_regdata[0x00], 0); } + uint32_t pan_left() const { return bitfield(m_regdata[0x01], 7); } + uint32_t pan_right() const { return bitfield(m_regdata[0x01], 6); } + uint32_t start_conversion() const { return bitfield(m_regdata[0x01], 3); } + uint32_t dac_enable() const { return bitfield(m_regdata[0x01], 2); } + uint32_t dram_8bit() const { return bitfield(m_regdata[0x01], 1); } + uint32_t rom_ram() const { return bitfield(m_regdata[0x01], 0); } + uint32_t start() const { return m_regdata[0x02] | (m_regdata[0x03] << 8); } + uint32_t end() const { return m_regdata[0x04] | (m_regdata[0x05] << 8); } + uint32_t prescale() const { return m_regdata[0x06] | (bitfield(m_regdata[0x07], 0, 3) << 8); } + uint32_t cpudata() const { return m_regdata[0x08]; } + uint32_t delta_n() const { return m_regdata[0x09] | (m_regdata[0x0a] << 8); } + uint32_t level() const { return m_regdata[0x0b]; } + uint32_t limit() const { return m_regdata[0x0c] | (m_regdata[0x0d] << 8); } + uint32_t dac() const { return m_regdata[0x0e]; } + uint32_t pcm() const { return m_regdata[0x0f]; } + +private: + // internal state + uint8_t m_regdata[REGISTERS]; // register data +}; + + +// ======================> adpcm_b_channel + +class adpcm_b_channel +{ + static constexpr int32_t STEP_MIN = 127; + static constexpr int32_t STEP_MAX = 24576; + +public: + static constexpr uint8_t STATUS_EOS = 0x01; + static constexpr uint8_t STATUS_BRDY = 0x02; + static constexpr uint8_t STATUS_PLAYING = 0x04; + + // constructor + adpcm_b_channel(adpcm_b_engine &owner, uint32_t addrshift); + + // reset the channel state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // signal key on/off + void keyonoff(bool on); + + // master clocking function + void clock(); + + // return the computed output value, with panning applied + template + void output(ymfm_output &output, uint32_t rshift) const; + + // return the status register + uint8_t status() const { return m_status; } + + // handle special register reads + uint8_t read(uint32_t regnum); + + // handle special register writes + void write(uint32_t regnum, uint8_t value); + +private: + // helper - return the current address shift + uint32_t address_shift() const; + + // load the start address + void load_start(); + + // limit checker; stops at the last byte of the chunk described by address_shift() + bool at_limit() const { return (m_curaddress == (((m_regs.limit() + 1) << address_shift()) - 1)); } + + // end checker; stops at the last byte of the chunk described by address_shift() + bool at_end() const { return (m_curaddress == (((m_regs.end() + 1) << address_shift()) - 1)); } + + // internal state + uint32_t const m_address_shift; // address bits shift-left + uint32_t m_status; // currently playing? + uint32_t m_curnibble; // index of the current nibble + uint32_t m_curbyte; // current byte of data + uint32_t m_dummy_read; // dummy read tracker + uint32_t m_position; // current fractional position + uint32_t m_curaddress; // current address + int32_t m_accumulator; // accumulator + int32_t m_prev_accum; // previous accumulator (for linear interp) + int32_t m_adpcm_step; // next forecast + adpcm_b_registers &m_regs; // reference to registers + adpcm_b_engine &m_owner; // reference to our owner +}; + + +// ======================> adpcm_b_engine + +class adpcm_b_engine +{ +public: + // constructor + adpcm_b_engine(ymfm_interface &intf, uint32_t addrshift = 0); + + // reset our status + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // master clocking function + void clock(); + + // compute sum of channel outputs + template + void output(ymfm_output &output, uint32_t rshift); + + // read from the ADPCM-B registers + uint32_t read(uint32_t regnum) { return m_channel->read(regnum); } + + // write to the ADPCM-B registers + void write(uint32_t regnum, uint8_t data); + + // status + uint8_t status() const { return m_channel->status(); } + + // return a reference to our interface + ymfm_interface &intf() { return m_intf; } + + // return a reference to our registers + adpcm_b_registers ®s() { return m_regs; } + +private: + // internal state + ymfm_interface &m_intf; // reference to our interface + std::unique_ptr m_channel; // channel pointer + adpcm_b_registers m_regs; // registers +}; + +} + +#endif // YMFM_ADPCM_H diff --git a/src/sound/ymfm/ymfm_fm.h b/src/sound/ymfm/ymfm_fm.h new file mode 100644 index 000000000..7c92c0f82 --- /dev/null +++ b/src/sound/ymfm/ymfm_fm.h @@ -0,0 +1,463 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_FM_H +#define YMFM_FM_H + +#pragma once + +#define DEBUG_LOG_WAVFILES (0) + +namespace ymfm +{ + +//********************************************************* +// GLOBAL ENUMERATORS +//********************************************************* + +// three different keyon sources; actual keyon is an OR over all of these +enum keyon_type : uint32_t +{ + KEYON_NORMAL = 0, + KEYON_RHYTHM = 1, + KEYON_CSM = 2 +}; + + + +//********************************************************* +// CORE IMPLEMENTATION +//********************************************************* + +// ======================> opdata_cache + +// this class holds data that is computed once at the start of clocking +// and remains static during subsequent sound generation +struct opdata_cache +{ + // set phase_step to this value to recalculate it each sample; needed + // in the case of PM LFO changes + static constexpr uint32_t PHASE_STEP_DYNAMIC = 1; + + uint16_t const *waveform; // base of sine table + uint32_t phase_step; // phase step, or PHASE_STEP_DYNAMIC if PM is active + uint32_t total_level; // total level * 8 + KSL + uint32_t block_freq; // raw block frequency value (used to compute phase_step) + int32_t detune; // detuning value (used to compute phase_step) + uint32_t multiple; // multiple value (x.1, used to compute phase_step) + uint32_t eg_sustain; // sustain level, shifted up to envelope values + uint8_t eg_rate[EG_STATES]; // envelope rate, including KSR + uint8_t eg_shift = 0; // envelope shift amount +}; + + +// ======================> fm_registers_base + +// base class for family-specific register classes; this provides a few +// constants, common defaults, and helpers, but mostly each derived class is +// responsible for defining all commonly-called methods +class fm_registers_base +{ +public: + // this value is returned from the write() function for rhythm channels + static constexpr uint32_t RHYTHM_CHANNEL = 0xff; + + // this is the size of a full sin waveform + static constexpr uint32_t WAVEFORM_LENGTH = 0x400; + + // + // the following constants need to be defined per family: + // uint32_t OUTPUTS: The number of outputs exposed (1-4) + // uint32_t CHANNELS: The number of channels on the chip + // uint32_t ALL_CHANNELS: A bitmask of all channels + // uint32_t OPERATORS: The number of operators on the chip + // uint32_t WAVEFORMS: The number of waveforms offered + // uint32_t REGISTERS: The number of 8-bit registers allocated + // uint32_t DEFAULT_PRESCALE: The starting clock prescale + // uint32_t EG_CLOCK_DIVIDER: The clock divider of the envelope generator + // uint32_t CSM_TRIGGER_MASK: Mask of channels to trigger in CSM mode + // uint32_t REG_MODE: The address of the "mode" register controlling timers + // uint8_t STATUS_TIMERA: Status bit to set when timer A fires + // uint8_t STATUS_TIMERB: Status bit to set when tiemr B fires + // uint8_t STATUS_BUSY: Status bit to set when the chip is busy + // uint8_t STATUS_IRQ: Status bit to set when an IRQ is signalled + // + // the following constants are uncommon: + // bool DYNAMIC_OPS: True if ops/channel can be changed at runtime (OPL3+) + // bool EG_HAS_DEPRESS: True if the chip has a DP ("depress"?) envelope stage (OPLL) + // bool EG_HAS_REVERB: True if the chip has a faux reverb envelope stage (OPQ/OPZ) + // bool EG_HAS_SSG: True if the chip has SSG envelope support (OPN) + // bool MODULATOR_DELAY: True if the modulator is delayed by 1 sample (OPL pre-OPL3) + // + static constexpr bool DYNAMIC_OPS = false; + static constexpr bool EG_HAS_DEPRESS = false; + static constexpr bool EG_HAS_REVERB = false; + static constexpr bool EG_HAS_SSG = false; + static constexpr bool MODULATOR_DELAY = false; + + // system-wide register defaults + uint32_t status_mask() const { return 0; } // OPL only + uint32_t irq_reset() const { return 0; } // OPL only + uint32_t noise_enable() const { return 0; } // OPM only + uint32_t rhythm_enable() const { return 0; } // OPL only + + // per-operator register defaults + uint32_t op_ssg_eg_enable(uint32_t opoffs) const { return 0; } // OPN(A) only + uint32_t op_ssg_eg_mode(uint32_t opoffs) const { return 0; } // OPN(A) only + +protected: + // helper to encode four operator numbers into a 32-bit value in the + // operator maps for each register class + static constexpr uint32_t operator_list(uint8_t o1 = 0xff, uint8_t o2 = 0xff, uint8_t o3 = 0xff, uint8_t o4 = 0xff) + { + return o1 | (o2 << 8) | (o3 << 16) | (o4 << 24); + } + + // helper to apply KSR to the raw ADSR rate, ignoring ksr if the + // raw value is 0, and clamping to 63 + static constexpr uint32_t effective_rate(uint32_t rawrate, uint32_t ksr) + { + return (rawrate == 0) ? 0 : std::min(rawrate + ksr, 63); + } +}; + + + +//********************************************************* +// CORE ENGINE CLASSES +//********************************************************* + +// forward declarations +template class fm_engine_base; + +// ======================> fm_operator + +// fm_operator represents an FM operator (or "slot" in FM parlance), which +// produces an output sine wave modulated by an envelope +template +class fm_operator +{ + // "quiet" value, used to optimize when we can skip doing work + static constexpr uint32_t EG_QUIET = 0x380; + +public: + // constructor + fm_operator(fm_engine_base &owner, uint32_t opoffs); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // reset the operator state + void reset(); + + // return the operator/channel offset + uint32_t opoffs() const { return m_opoffs; } + uint32_t choffs() const { return m_choffs; } + + // set the current channel + void set_choffs(uint32_t choffs) { m_choffs = choffs; } + + // prepare prior to clocking + bool prepare(); + + // master clocking function + void clock(uint32_t env_counter, int32_t lfo_raw_pm); + + // return the current phase value + uint32_t phase() const { return m_phase >> 10; } + + // compute operator volume + int32_t compute_volume(uint32_t phase, uint32_t am_offset) const; + + // compute volume for the OPM noise channel + int32_t compute_noise_volume(uint32_t am_offset) const; + + // key state control + void keyonoff(uint32_t on, keyon_type type); + + // return a reference to our registers + RegisterType ®s() const { return m_regs; } + + // simple getters for debugging + envelope_state debug_eg_state() const { return m_env_state; } + uint16_t debug_eg_attenuation() const { return m_env_attenuation; } + uint8_t debug_ssg_inverted() const { return m_ssg_inverted; } + opdata_cache &debug_cache() { return m_cache; } + +private: + // start the attack phase + void start_attack(bool is_restart = false); + + // start the release phase + void start_release(); + + // clock phases + void clock_keystate(uint32_t keystate); + void clock_ssg_eg_state(); + void clock_envelope(uint32_t env_counter); + void clock_phase(int32_t lfo_raw_pm); + + // return effective attenuation of the envelope + uint32_t envelope_attenuation(uint32_t am_offset) const; + + // internal state + uint32_t m_choffs; // channel offset in registers + uint32_t m_opoffs; // operator offset in registers + uint32_t m_phase; // current phase value (10.10 format) + uint16_t m_env_attenuation; // computed envelope attenuation (4.6 format) + envelope_state m_env_state; // current envelope state + uint8_t m_ssg_inverted; // non-zero if the output should be inverted (bit 0) + uint8_t m_key_state; // current key state: on or off (bit 0) + uint8_t m_keyon_live; // live key on state (bit 0 = direct, bit 1 = rhythm, bit 2 = CSM) + opdata_cache m_cache; // cached values for performance + RegisterType &m_regs; // direct reference to registers + fm_engine_base &m_owner; // reference to the owning engine +}; + + +// ======================> fm_channel + +// fm_channel represents an FM channel which combines the output of 2 or 4 +// operators into a final result +template +class fm_channel +{ + using output_data = ymfm_output; + +public: + // constructor + fm_channel(fm_engine_base &owner, uint32_t choffs); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // reset the channel state + void reset(); + + // return the channel offset + uint32_t choffs() const { return m_choffs; } + + // assign operators + void assign(uint32_t index, fm_operator *op) + { + assert(index < array_size(m_op)); + m_op[index] = op; + if (op != nullptr) + op->set_choffs(m_choffs); + } + + // signal key on/off to our operators + void keyonoff(uint32_t states, keyon_type type, uint32_t chnum); + + // prepare prior to clocking + bool prepare(); + + // master clocking function + void clock(uint32_t env_counter, int32_t lfo_raw_pm); + + // specific 2-operator and 4-operator output handlers + void output_2op(output_data &output, uint32_t rshift, int32_t clipmax) const; + void output_4op(output_data &output, uint32_t rshift, int32_t clipmax) const; + + // compute the special OPL rhythm channel outputs + void output_rhythm_ch6(output_data &output, uint32_t rshift, int32_t clipmax) const; + void output_rhythm_ch7(uint32_t phase_select, output_data &output, uint32_t rshift, int32_t clipmax) const; + void output_rhythm_ch8(uint32_t phase_select, output_data &output, uint32_t rshift, int32_t clipmax) const; + + // are we a 4-operator channel or a 2-operator one? + bool is4op() const + { + if (RegisterType::DYNAMIC_OPS) + return (m_op[2] != nullptr); + return (RegisterType::OPERATORS / RegisterType::CHANNELS == 4); + } + + // return a reference to our registers + RegisterType ®s() const { return m_regs; } + + // simple getters for debugging + fm_operator *debug_operator(uint32_t index) const { return m_op[index]; } + +private: + // helper to add values to the outputs based on channel enables + void add_to_output(uint32_t choffs, output_data &output, int32_t value) const + { + // create these constants to appease overzealous compilers checking array + // bounds in unreachable code (looking at you, clang) + constexpr int out0_index = 0; + constexpr int out1_index = 1 % RegisterType::OUTPUTS; + constexpr int out2_index = 2 % RegisterType::OUTPUTS; + constexpr int out3_index = 3 % RegisterType::OUTPUTS; + + if (RegisterType::OUTPUTS == 1 || m_regs.ch_output_0(choffs)) + output.data[out0_index] += value; + if (RegisterType::OUTPUTS >= 2 && m_regs.ch_output_1(choffs)) + output.data[out1_index] += value; + if (RegisterType::OUTPUTS >= 3 && m_regs.ch_output_2(choffs)) + output.data[out2_index] += value; + if (RegisterType::OUTPUTS >= 4 && m_regs.ch_output_3(choffs)) + output.data[out3_index] += value; + } + + // internal state + 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 + RegisterType &m_regs; // direct reference to registers + fm_engine_base &m_owner; // reference to the owning engine +}; + + +// ======================> fm_engine_base + +// fm_engine_base represents a set of operators and channels which together +// form a Yamaha FM core; chips that implement other engines (ADPCM, wavetable, +// etc) take this output and combine it with the others externally +template +class fm_engine_base : public ymfm_engine_callbacks +{ +public: + // expose some constants from the registers + static constexpr uint32_t OUTPUTS = RegisterType::OUTPUTS; + static constexpr uint32_t CHANNELS = RegisterType::CHANNELS; + static constexpr uint32_t ALL_CHANNELS = RegisterType::ALL_CHANNELS; + static constexpr uint32_t OPERATORS = RegisterType::OPERATORS; + + // also expose status flags for consumers that inject additional bits + static constexpr uint8_t STATUS_TIMERA = RegisterType::STATUS_TIMERA; + static constexpr uint8_t STATUS_TIMERB = RegisterType::STATUS_TIMERB; + static constexpr uint8_t STATUS_BUSY = RegisterType::STATUS_BUSY; + static constexpr uint8_t STATUS_IRQ = RegisterType::STATUS_IRQ; + + // expose the correct output class + using output_data = ymfm_output; + + // constructor + fm_engine_base(ymfm_interface &intf); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // reset the overall state + void reset(); + + // master clocking function + uint32_t clock(uint32_t chanmask); + + // compute sum of channel outputs + void output(output_data &output, uint32_t rshift, int32_t clipmax, uint32_t chanmask) const; + + // write to the OPN registers + void write(uint16_t regnum, uint8_t data); + + // return the current status + uint8_t status() const; + + // set/reset bits in the status register, updating the IRQ status + uint8_t set_reset_status(uint8_t set, uint8_t reset) + { + m_status = (m_status | set) & ~(reset | STATUS_BUSY); + m_intf.ymfm_sync_check_interrupts(); + return m_status & ~m_regs.status_mask(); + } + + // set the IRQ mask + void set_irq_mask(uint8_t mask) { m_irq_mask = mask; m_intf.ymfm_sync_check_interrupts(); } + + // return the current clock prescale + uint32_t clock_prescale() const { return m_clock_prescale; } + + // set prescale factor (2/3/6) + void set_clock_prescale(uint32_t prescale) { m_clock_prescale = prescale; } + + // compute sample rate + uint32_t sample_rate(uint32_t baseclock) const + { +#if (DEBUG_LOG_WAVFILES) + for (uint32_t chnum = 0; chnum < CHANNELS; chnum++) + m_wavfile[chnum].set_samplerate(baseclock / (m_clock_prescale * OPERATORS)); +#endif + return baseclock / (m_clock_prescale * OPERATORS); + } + + // return the owning device + ymfm_interface &intf() const { return m_intf; } + + // return a reference to our registers + RegisterType ®s() { return m_regs; } + + // invalidate any caches + void invalidate_caches() { m_modified_channels = RegisterType::ALL_CHANNELS; } + + // simple getters for debugging + fm_channel *debug_channel(uint32_t index) const { return m_channel[index].get(); } + fm_operator *debug_operator(uint32_t index) const { return m_operator[index].get(); } + +public: + // timer callback; called by the interface when a timer fires + virtual void engine_timer_expired(uint32_t tnum) override; + + // check interrupts; called by the interface after synchronization + virtual void engine_check_interrupts() override; + + // mode register write; called by the interface after synchronization + virtual void engine_mode_write(uint8_t data) override; + +protected: + // assign the current set of operators to channels + void assign_operators(); + + // update the state of the given timer + void update_timer(uint32_t which, uint32_t enable, int32_t delta_clocks); + + // internal state + ymfm_interface &m_intf; // reference to the system interface + uint32_t m_env_counter; // envelope counter; low 2 bits are sub-counter + uint8_t m_status; // current status register + uint8_t m_clock_prescale; // prescale factor (2/3/6) + uint8_t m_irq_mask; // mask of which bits signal IRQs + uint8_t m_irq_state; // current IRQ state + uint8_t m_timer_running[2]; // current timer running state + uint8_t m_total_clocks; // low 8 bits of the total number of clocks processed + uint32_t m_active_channels; // mask of active channels (computed by prepare) + uint32_t m_modified_channels; // mask of channels that have been modified + uint32_t m_prepare_count; // counter to do periodic prepare sweeps + RegisterType m_regs; // register accessor + std::unique_ptr> m_channel[CHANNELS]; // channel pointers + std::unique_ptr> m_operator[OPERATORS]; // operator pointers +#if (DEBUG_LOG_WAVFILES) + mutable ymfm_wavfile<1> m_wavfile[CHANNELS]; // for debugging +#endif +}; + +} + +#endif // YMFM_FM_H diff --git a/src/sound/ymfm/ymfm_fm.ipp b/src/sound/ymfm/ymfm_fm.ipp new file mode 100644 index 000000000..17bbc9150 --- /dev/null +++ b/src/sound/ymfm/ymfm_fm.ipp @@ -0,0 +1,1589 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +namespace ymfm +{ + +//********************************************************* +// GLOBAL TABLE LOOKUPS +//********************************************************* + +//------------------------------------------------- +// abs_sin_attenuation - given a sin (phase) input +// where the range 0-2*PI is mapped onto 10 bits, +// return the absolute value of sin(input), +// logarithmically-adjusted and treated as an +// attenuation value, in 4.8 fixed point format +//------------------------------------------------- + +inline uint32_t abs_sin_attenuation(uint32_t input) +{ + // the values here are stored as 4.8 logarithmic values for 1/4 phase + // this matches the internal format of the OPN chip, extracted from the die + static uint16_t const s_sin_table[256] = + { + 0x859,0x6c3,0x607,0x58b,0x52e,0x4e4,0x4a6,0x471,0x443,0x41a,0x3f5,0x3d3,0x3b5,0x398,0x37e,0x365, + 0x34e,0x339,0x324,0x311,0x2ff,0x2ed,0x2dc,0x2cd,0x2bd,0x2af,0x2a0,0x293,0x286,0x279,0x26d,0x261, + 0x256,0x24b,0x240,0x236,0x22c,0x222,0x218,0x20f,0x206,0x1fd,0x1f5,0x1ec,0x1e4,0x1dc,0x1d4,0x1cd, + 0x1c5,0x1be,0x1b7,0x1b0,0x1a9,0x1a2,0x19b,0x195,0x18f,0x188,0x182,0x17c,0x177,0x171,0x16b,0x166, + 0x160,0x15b,0x155,0x150,0x14b,0x146,0x141,0x13c,0x137,0x133,0x12e,0x129,0x125,0x121,0x11c,0x118, + 0x114,0x10f,0x10b,0x107,0x103,0x0ff,0x0fb,0x0f8,0x0f4,0x0f0,0x0ec,0x0e9,0x0e5,0x0e2,0x0de,0x0db, + 0x0d7,0x0d4,0x0d1,0x0cd,0x0ca,0x0c7,0x0c4,0x0c1,0x0be,0x0bb,0x0b8,0x0b5,0x0b2,0x0af,0x0ac,0x0a9, + 0x0a7,0x0a4,0x0a1,0x09f,0x09c,0x099,0x097,0x094,0x092,0x08f,0x08d,0x08a,0x088,0x086,0x083,0x081, + 0x07f,0x07d,0x07a,0x078,0x076,0x074,0x072,0x070,0x06e,0x06c,0x06a,0x068,0x066,0x064,0x062,0x060, + 0x05e,0x05c,0x05b,0x059,0x057,0x055,0x053,0x052,0x050,0x04e,0x04d,0x04b,0x04a,0x048,0x046,0x045, + 0x043,0x042,0x040,0x03f,0x03e,0x03c,0x03b,0x039,0x038,0x037,0x035,0x034,0x033,0x031,0x030,0x02f, + 0x02e,0x02d,0x02b,0x02a,0x029,0x028,0x027,0x026,0x025,0x024,0x023,0x022,0x021,0x020,0x01f,0x01e, + 0x01d,0x01c,0x01b,0x01a,0x019,0x018,0x017,0x017,0x016,0x015,0x014,0x014,0x013,0x012,0x011,0x011, + 0x010,0x00f,0x00f,0x00e,0x00d,0x00d,0x00c,0x00c,0x00b,0x00a,0x00a,0x009,0x009,0x008,0x008,0x007, + 0x007,0x007,0x006,0x006,0x005,0x005,0x005,0x004,0x004,0x004,0x003,0x003,0x003,0x002,0x002,0x002, + 0x002,0x001,0x001,0x001,0x001,0x001,0x001,0x001,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000 + }; + + // if the top bit is set, we're in the second half of the curve + // which is a mirror image, so invert the index + if (bitfield(input, 8)) + input = ~input; + + // return the value from the table + return s_sin_table[input & 0xff]; +} + + +//------------------------------------------------- +// attenuation_to_volume - given a 5.8 fixed point +// logarithmic attenuation value, return a 13-bit +// linear volume +//------------------------------------------------- + +inline uint32_t attenuation_to_volume(uint32_t input) +{ + // the values here are 10-bit mantissas with an implied leading bit + // this matches the internal format of the OPN chip, extracted from the die + + // as a nod to performance, the implicit 0x400 bit is pre-incorporated, and + // the values are left-shifted by 2 so that a simple right shift is all that + // is needed; also the order is reversed to save a NOT on the input +#define X(a) (((a) | 0x400) << 2) + static uint16_t const s_power_table[256] = + { + X(0x3fa),X(0x3f5),X(0x3ef),X(0x3ea),X(0x3e4),X(0x3df),X(0x3da),X(0x3d4), + X(0x3cf),X(0x3c9),X(0x3c4),X(0x3bf),X(0x3b9),X(0x3b4),X(0x3ae),X(0x3a9), + X(0x3a4),X(0x39f),X(0x399),X(0x394),X(0x38f),X(0x38a),X(0x384),X(0x37f), + X(0x37a),X(0x375),X(0x370),X(0x36a),X(0x365),X(0x360),X(0x35b),X(0x356), + X(0x351),X(0x34c),X(0x347),X(0x342),X(0x33d),X(0x338),X(0x333),X(0x32e), + X(0x329),X(0x324),X(0x31f),X(0x31a),X(0x315),X(0x310),X(0x30b),X(0x306), + X(0x302),X(0x2fd),X(0x2f8),X(0x2f3),X(0x2ee),X(0x2e9),X(0x2e5),X(0x2e0), + X(0x2db),X(0x2d6),X(0x2d2),X(0x2cd),X(0x2c8),X(0x2c4),X(0x2bf),X(0x2ba), + X(0x2b5),X(0x2b1),X(0x2ac),X(0x2a8),X(0x2a3),X(0x29e),X(0x29a),X(0x295), + X(0x291),X(0x28c),X(0x288),X(0x283),X(0x27f),X(0x27a),X(0x276),X(0x271), + X(0x26d),X(0x268),X(0x264),X(0x25f),X(0x25b),X(0x257),X(0x252),X(0x24e), + X(0x249),X(0x245),X(0x241),X(0x23c),X(0x238),X(0x234),X(0x230),X(0x22b), + X(0x227),X(0x223),X(0x21e),X(0x21a),X(0x216),X(0x212),X(0x20e),X(0x209), + X(0x205),X(0x201),X(0x1fd),X(0x1f9),X(0x1f5),X(0x1f0),X(0x1ec),X(0x1e8), + X(0x1e4),X(0x1e0),X(0x1dc),X(0x1d8),X(0x1d4),X(0x1d0),X(0x1cc),X(0x1c8), + X(0x1c4),X(0x1c0),X(0x1bc),X(0x1b8),X(0x1b4),X(0x1b0),X(0x1ac),X(0x1a8), + X(0x1a4),X(0x1a0),X(0x19c),X(0x199),X(0x195),X(0x191),X(0x18d),X(0x189), + X(0x185),X(0x181),X(0x17e),X(0x17a),X(0x176),X(0x172),X(0x16f),X(0x16b), + X(0x167),X(0x163),X(0x160),X(0x15c),X(0x158),X(0x154),X(0x151),X(0x14d), + X(0x149),X(0x146),X(0x142),X(0x13e),X(0x13b),X(0x137),X(0x134),X(0x130), + X(0x12c),X(0x129),X(0x125),X(0x122),X(0x11e),X(0x11b),X(0x117),X(0x114), + X(0x110),X(0x10c),X(0x109),X(0x106),X(0x102),X(0x0ff),X(0x0fb),X(0x0f8), + X(0x0f4),X(0x0f1),X(0x0ed),X(0x0ea),X(0x0e7),X(0x0e3),X(0x0e0),X(0x0dc), + X(0x0d9),X(0x0d6),X(0x0d2),X(0x0cf),X(0x0cc),X(0x0c8),X(0x0c5),X(0x0c2), + X(0x0be),X(0x0bb),X(0x0b8),X(0x0b5),X(0x0b1),X(0x0ae),X(0x0ab),X(0x0a8), + X(0x0a4),X(0x0a1),X(0x09e),X(0x09b),X(0x098),X(0x094),X(0x091),X(0x08e), + X(0x08b),X(0x088),X(0x085),X(0x082),X(0x07e),X(0x07b),X(0x078),X(0x075), + X(0x072),X(0x06f),X(0x06c),X(0x069),X(0x066),X(0x063),X(0x060),X(0x05d), + X(0x05a),X(0x057),X(0x054),X(0x051),X(0x04e),X(0x04b),X(0x048),X(0x045), + X(0x042),X(0x03f),X(0x03c),X(0x039),X(0x036),X(0x033),X(0x030),X(0x02d), + X(0x02a),X(0x028),X(0x025),X(0x022),X(0x01f),X(0x01c),X(0x019),X(0x016), + X(0x014),X(0x011),X(0x00e),X(0x00b),X(0x008),X(0x006),X(0x003),X(0x000) + }; +#undef X + + // look up the fractional part, then shift by the whole + return s_power_table[input & 0xff] >> (input >> 8); +} + + +//------------------------------------------------- +// attenuation_increment - given a 6-bit ADSR +// rate value and a 3-bit stepping index, +// return a 4-bit increment to the attenutaion +// for this step (or for the attack case, the +// fractional scale factor to decrease by) +//------------------------------------------------- + +inline uint32_t attenuation_increment(uint32_t rate, uint32_t index) +{ + static uint32_t const s_increment_table[64] = + { + 0x00000000, 0x00000000, 0x10101010, 0x10101010, // 0-3 (0x00-0x03) + 0x10101010, 0x10101010, 0x11101110, 0x11101110, // 4-7 (0x04-0x07) + 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 8-11 (0x08-0x0B) + 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 12-15 (0x0C-0x0F) + 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 16-19 (0x10-0x13) + 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 20-23 (0x14-0x17) + 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 24-27 (0x18-0x1B) + 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 28-31 (0x1C-0x1F) + 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 32-35 (0x20-0x23) + 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 36-39 (0x24-0x27) + 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 40-43 (0x28-0x2B) + 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 44-47 (0x2C-0x2F) + 0x11111111, 0x21112111, 0x21212121, 0x22212221, // 48-51 (0x30-0x33) + 0x22222222, 0x42224222, 0x42424242, 0x44424442, // 52-55 (0x34-0x37) + 0x44444444, 0x84448444, 0x84848484, 0x88848884, // 56-59 (0x38-0x3B) + 0x88888888, 0x88888888, 0x88888888, 0x88888888 // 60-63 (0x3C-0x3F) + }; + return bitfield(s_increment_table[rate], 4*index, 4); +} + + +//------------------------------------------------- +// detune_adjustment - given a 5-bit key code +// value and a 3-bit detune parameter, return a +// 6-bit signed phase displacement; this table +// has been verified against Nuked's equations, +// but the equations are rather complicated, so +// we'll keep the simplicity of the table +//------------------------------------------------- + +inline int32_t detune_adjustment(uint32_t detune, uint32_t keycode) +{ + static uint8_t const s_detune_adjustment[32][4] = + { + { 0, 0, 1, 2 }, { 0, 0, 1, 2 }, { 0, 0, 1, 2 }, { 0, 0, 1, 2 }, + { 0, 1, 2, 2 }, { 0, 1, 2, 3 }, { 0, 1, 2, 3 }, { 0, 1, 2, 3 }, + { 0, 1, 2, 4 }, { 0, 1, 3, 4 }, { 0, 1, 3, 4 }, { 0, 1, 3, 5 }, + { 0, 2, 4, 5 }, { 0, 2, 4, 6 }, { 0, 2, 4, 6 }, { 0, 2, 5, 7 }, + { 0, 2, 5, 8 }, { 0, 3, 6, 8 }, { 0, 3, 6, 9 }, { 0, 3, 7, 10 }, + { 0, 4, 8, 11 }, { 0, 4, 8, 12 }, { 0, 4, 9, 13 }, { 0, 5, 10, 14 }, + { 0, 5, 11, 16 }, { 0, 6, 12, 17 }, { 0, 6, 13, 19 }, { 0, 7, 14, 20 }, + { 0, 8, 16, 22 }, { 0, 8, 16, 22 }, { 0, 8, 16, 22 }, { 0, 8, 16, 22 } + }; + int32_t result = s_detune_adjustment[keycode][detune & 3]; + return bitfield(detune, 2) ? -result : result; +} + + +//------------------------------------------------- +// opm_key_code_to_phase_step - converts an +// OPM concatenated block (3 bits), keycode +// (4 bits) and key fraction (6 bits) to a 0.10 +// phase step, after applying the given delta; +// this applies to OPM and OPZ, so it lives here +// in a central location +//------------------------------------------------- + +inline uint32_t opm_key_code_to_phase_step(uint32_t block_freq, int32_t delta) +{ + // The phase step is essentially the fnum in OPN-speak. To compute this table, + // we used the standard formula for computing the frequency of a note, and + // then converted that frequency to fnum using the formula documented in the + // YM2608 manual. + // + // However, the YM2608 manual describes everything in terms of a nominal 8MHz + // clock, which produces an FM clock of: + // + // 8000000 / 24(operators) / 6(prescale) = 55555Hz FM clock + // + // Whereas the descriptions for the YM2151 use a nominal 3.579545MHz clock: + // + // 3579545 / 32(operators) / 2(prescale) = 55930Hz FM clock + // + // To correct for this, the YM2608 formula was adjusted to use a clock of + // 8053920Hz, giving this equation for the fnum: + // + // fnum = (double(144) * freq * (1 << 20)) / double(8053920) / 4; + // + // Unfortunately, the computed table differs in a few spots from the data + // verified from an actual chip. The table below comes from David Viens' + // analysis, used with his permission. + static const uint32_t s_phase_step[12*64] = + { + 41568,41600,41632,41664,41696,41728,41760,41792,41856,41888,41920,41952,42016,42048,42080,42112, + 42176,42208,42240,42272,42304,42336,42368,42400,42464,42496,42528,42560,42624,42656,42688,42720, + 42784,42816,42848,42880,42912,42944,42976,43008,43072,43104,43136,43168,43232,43264,43296,43328, + 43392,43424,43456,43488,43552,43584,43616,43648,43712,43744,43776,43808,43872,43904,43936,43968, + 44032,44064,44096,44128,44192,44224,44256,44288,44352,44384,44416,44448,44512,44544,44576,44608, + 44672,44704,44736,44768,44832,44864,44896,44928,44992,45024,45056,45088,45152,45184,45216,45248, + 45312,45344,45376,45408,45472,45504,45536,45568,45632,45664,45728,45760,45792,45824,45888,45920, + 45984,46016,46048,46080,46144,46176,46208,46240,46304,46336,46368,46400,46464,46496,46528,46560, + 46656,46688,46720,46752,46816,46848,46880,46912,46976,47008,47072,47104,47136,47168,47232,47264, + 47328,47360,47392,47424,47488,47520,47552,47584,47648,47680,47744,47776,47808,47840,47904,47936, + 48032,48064,48096,48128,48192,48224,48288,48320,48384,48416,48448,48480,48544,48576,48640,48672, + 48736,48768,48800,48832,48896,48928,48992,49024,49088,49120,49152,49184,49248,49280,49344,49376, + 49440,49472,49504,49536,49600,49632,49696,49728,49792,49824,49856,49888,49952,49984,50048,50080, + 50144,50176,50208,50240,50304,50336,50400,50432,50496,50528,50560,50592,50656,50688,50752,50784, + 50880,50912,50944,50976,51040,51072,51136,51168,51232,51264,51328,51360,51424,51456,51488,51520, + 51616,51648,51680,51712,51776,51808,51872,51904,51968,52000,52064,52096,52160,52192,52224,52256, + 52384,52416,52448,52480,52544,52576,52640,52672,52736,52768,52832,52864,52928,52960,52992,53024, + 53120,53152,53216,53248,53312,53344,53408,53440,53504,53536,53600,53632,53696,53728,53792,53824, + 53920,53952,54016,54048,54112,54144,54208,54240,54304,54336,54400,54432,54496,54528,54592,54624, + 54688,54720,54784,54816,54880,54912,54976,55008,55072,55104,55168,55200,55264,55296,55360,55392, + 55488,55520,55584,55616,55680,55712,55776,55808,55872,55936,55968,56032,56064,56128,56160,56224, + 56288,56320,56384,56416,56480,56512,56576,56608,56672,56736,56768,56832,56864,56928,56960,57024, + 57120,57152,57216,57248,57312,57376,57408,57472,57536,57568,57632,57664,57728,57792,57824,57888, + 57952,57984,58048,58080,58144,58208,58240,58304,58368,58400,58464,58496,58560,58624,58656,58720, + 58784,58816,58880,58912,58976,59040,59072,59136,59200,59232,59296,59328,59392,59456,59488,59552, + 59648,59680,59744,59776,59840,59904,59936,60000,60064,60128,60160,60224,60288,60320,60384,60416, + 60512,60544,60608,60640,60704,60768,60800,60864,60928,60992,61024,61088,61152,61184,61248,61280, + 61376,61408,61472,61536,61600,61632,61696,61760,61824,61856,61920,61984,62048,62080,62144,62208, + 62272,62304,62368,62432,62496,62528,62592,62656,62720,62752,62816,62880,62944,62976,63040,63104, + 63200,63232,63296,63360,63424,63456,63520,63584,63648,63680,63744,63808,63872,63904,63968,64032, + 64096,64128,64192,64256,64320,64352,64416,64480,64544,64608,64672,64704,64768,64832,64896,64928, + 65024,65056,65120,65184,65248,65312,65376,65408,65504,65536,65600,65664,65728,65792,65856,65888, + 65984,66016,66080,66144,66208,66272,66336,66368,66464,66496,66560,66624,66688,66752,66816,66848, + 66944,66976,67040,67104,67168,67232,67296,67328,67424,67456,67520,67584,67648,67712,67776,67808, + 67904,67936,68000,68064,68128,68192,68256,68288,68384,68448,68512,68544,68640,68672,68736,68800, + 68896,68928,68992,69056,69120,69184,69248,69280,69376,69440,69504,69536,69632,69664,69728,69792, + 69920,69952,70016,70080,70144,70208,70272,70304,70400,70464,70528,70560,70656,70688,70752,70816, + 70912,70976,71040,71104,71136,71232,71264,71360,71424,71488,71552,71616,71648,71744,71776,71872, + 71968,72032,72096,72160,72192,72288,72320,72416,72480,72544,72608,72672,72704,72800,72832,72928, + 72992,73056,73120,73184,73216,73312,73344,73440,73504,73568,73632,73696,73728,73824,73856,73952, + 74080,74144,74208,74272,74304,74400,74432,74528,74592,74656,74720,74784,74816,74912,74944,75040, + 75136,75200,75264,75328,75360,75456,75488,75584,75648,75712,75776,75840,75872,75968,76000,76096, + 76224,76288,76352,76416,76448,76544,76576,76672,76736,76800,76864,76928,77024,77120,77152,77248, + 77344,77408,77472,77536,77568,77664,77696,77792,77856,77920,77984,78048,78144,78240,78272,78368, + 78464,78528,78592,78656,78688,78784,78816,78912,78976,79040,79104,79168,79264,79360,79392,79488, + 79616,79680,79744,79808,79840,79936,79968,80064,80128,80192,80256,80320,80416,80512,80544,80640, + 80768,80832,80896,80960,80992,81088,81120,81216,81280,81344,81408,81472,81568,81664,81696,81792, + 81952,82016,82080,82144,82176,82272,82304,82400,82464,82528,82592,82656,82752,82848,82880,82976 + }; + + // extract the block (octave) first + uint32_t block = bitfield(block_freq, 10, 3); + + // the keycode (bits 6-9) is "gappy", mapping 12 values over 16 in each + // octave; to correct for this, we multiply the 4-bit value by 3/4 (or + // rather subtract 1/4); note that a (invalid) value of 15 will bleed into + // the next octave -- this is confirmed + uint32_t adjusted_code = bitfield(block_freq, 6, 4) - bitfield(block_freq, 8, 2); + + // now re-insert the 6-bit fraction + int32_t eff_freq = (adjusted_code << 6) | bitfield(block_freq, 0, 6); + + // now that the gaps are removed, add the delta + eff_freq += delta; + + // handle over/underflow by adjusting the block: + if (uint32_t(eff_freq) >= 768) + { + // minimum delta is -512 (PM), so we can only underflow by 1 octave + if (eff_freq < 0) + { + eff_freq += 768; + if (block-- == 0) + return s_phase_step[0] >> 7; + } + + // maximum delta is +512+608 (PM+detune), so we can overflow by up to 2 octaves + else + { + eff_freq -= 768; + if (eff_freq >= 768) + block++, eff_freq -= 768; + if (block++ >= 7) + return s_phase_step[767]; + } + } + + // look up the phase shift for the key code, then shift by octave + return s_phase_step[eff_freq] >> (block ^ 7); +} + + +//------------------------------------------------- +// opn_lfo_pm_phase_adjustment - given the 7 most +// significant frequency number bits, plus a 3-bit +// PM depth value and a signed 5-bit raw PM value, +// return a signed PM adjustment to the frequency; +// algorithm written to match Nuked behavior +//------------------------------------------------- + +inline int32_t opn_lfo_pm_phase_adjustment(uint32_t fnum_bits, uint32_t pm_sensitivity, int32_t lfo_raw_pm) +{ + // this table encodes 2 shift values to apply to the top 7 bits + // of fnum; it is effectively a cheap multiply by a constant + // value containing 0-2 bits + static uint8_t const s_lfo_pm_shifts[8][8] = + { + { 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77 }, + { 0x77, 0x77, 0x77, 0x77, 0x72, 0x72, 0x72, 0x72 }, + { 0x77, 0x77, 0x77, 0x72, 0x72, 0x72, 0x17, 0x17 }, + { 0x77, 0x77, 0x72, 0x72, 0x17, 0x17, 0x12, 0x12 }, + { 0x77, 0x77, 0x72, 0x17, 0x17, 0x17, 0x12, 0x07 }, + { 0x77, 0x77, 0x17, 0x12, 0x07, 0x07, 0x02, 0x01 }, + { 0x77, 0x77, 0x17, 0x12, 0x07, 0x07, 0x02, 0x01 }, + { 0x77, 0x77, 0x17, 0x12, 0x07, 0x07, 0x02, 0x01 } + }; + + // look up the relevant shifts + int32_t abs_pm = (lfo_raw_pm < 0) ? -lfo_raw_pm : lfo_raw_pm; + uint32_t const shifts = s_lfo_pm_shifts[pm_sensitivity][bitfield(abs_pm, 0, 3)]; + + // compute the adjustment + int32_t adjust = (fnum_bits >> bitfield(shifts, 0, 4)) + (fnum_bits >> bitfield(shifts, 4, 4)); + if (pm_sensitivity > 5) + adjust <<= pm_sensitivity - 5; + adjust >>= 2; + + // every 16 cycles it inverts sign + return (lfo_raw_pm < 0) ? -adjust : adjust; +} + + + +//********************************************************* +// FM OPERATOR +//********************************************************* + +//------------------------------------------------- +// fm_operator - constructor +//------------------------------------------------- + +template +fm_operator::fm_operator(fm_engine_base &owner, uint32_t opoffs) : + m_choffs(0), + m_opoffs(opoffs), + m_phase(0), + m_env_attenuation(0x3ff), + m_env_state(EG_RELEASE), + m_ssg_inverted(false), + m_key_state(0), + m_keyon_live(0), + m_regs(owner.regs()), + m_owner(owner) +{ +} + + +//------------------------------------------------- +// reset - reset the channel state +//------------------------------------------------- + +template +void fm_operator::reset() +{ + // reset our data + m_phase = 0; + m_env_attenuation = 0x3ff; + m_env_state = EG_RELEASE; + m_ssg_inverted = 0; + m_key_state = 0; + m_keyon_live = 0; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +template +void fm_operator::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_phase); + state.save_restore(m_env_attenuation); + state.save_restore(m_env_state); + state.save_restore(m_ssg_inverted); + state.save_restore(m_key_state); + state.save_restore(m_keyon_live); +} + + +//------------------------------------------------- +// prepare - prepare for clocking +//------------------------------------------------- + +template +bool fm_operator::prepare() +{ + // cache the data + m_regs.cache_operator_data(m_choffs, m_opoffs, m_cache); + + // clock the key state + clock_keystate(uint32_t(m_keyon_live != 0)); + m_keyon_live &= ~(1 << KEYON_CSM); + + // we're active until we're quiet after the release + return (m_env_state != (RegisterType::EG_HAS_REVERB ? EG_REVERB : EG_RELEASE) || m_env_attenuation < EG_QUIET); +} + + +//------------------------------------------------- +// clock - master clocking function +//------------------------------------------------- + +template +void fm_operator::clock(uint32_t env_counter, int32_t lfo_raw_pm) +{ + // clock the SSG-EG state (OPN/OPNA) + if (m_regs.op_ssg_eg_enable(m_opoffs)) + clock_ssg_eg_state(); + else + m_ssg_inverted = false; + + // clock the envelope if on an envelope cycle; env_counter is a x.2 value + if (bitfield(env_counter, 0, 2) == 0) + clock_envelope(env_counter >> 2); + + // clock the phase + clock_phase(lfo_raw_pm); +} + + +//------------------------------------------------- +// compute_volume - compute the 14-bit signed +// volume of this operator, given a phase +// modulation and an AM LFO offset +//------------------------------------------------- + +template +int32_t fm_operator::compute_volume(uint32_t phase, uint32_t am_offset) const +{ + // the low 10 bits of phase represents a full 2*PI period over + // the full sin wave + + // early out if the envelope is effectively off + if (m_env_attenuation > EG_QUIET) + return 0; + + // get the absolute value of the sin, as attenuation, as a 4.8 fixed point value + uint32_t sin_attenuation = m_cache.waveform[phase & (RegisterType::WAVEFORM_LENGTH - 1)]; + + // get the attenuation from the evelope generator as a 4.6 value, shifted up to 4.8 + uint32_t env_attenuation = envelope_attenuation(am_offset) << 2; + + // combine into a 5.8 value, then convert from attenuation to 13-bit linear volume + int32_t result = attenuation_to_volume((sin_attenuation & 0x7fff) + env_attenuation); + + // negate if in the negative part of the sin wave (sign bit gives 14 bits) + return bitfield(sin_attenuation, 15) ? -result : result; +} + + +//------------------------------------------------- +// compute_noise_volume - compute the 14-bit +// signed noise volume of this operator, given a +// noise input value and an AM offset +//------------------------------------------------- + +template +int32_t fm_operator::compute_noise_volume(uint32_t am_offset) const +{ + // application manual says the logarithmic transform is not applied here, so we + // just use the raw envelope attenuation, inverted (since 0 attenuation should be + // maximum), and shift it up from a 10-bit value to an 11-bit value + int32_t result = (envelope_attenuation(am_offset) ^ 0x3ff) << 1; + + // QUESTION: is AM applied still? + + // negate based on the noise state + return bitfield(m_regs.noise_state(), 0) ? -result : result; +} + + +//------------------------------------------------- +// keyonoff - signal a key on/off event +//------------------------------------------------- + +template +void fm_operator::keyonoff(uint32_t on, keyon_type type) +{ + m_keyon_live = (m_keyon_live & ~(1 << int(type))) | (bitfield(on, 0) << int(type)); +} + + +//------------------------------------------------- +// start_attack - start the attack phase; called +// when a keyon happens or when an SSG-EG cycle +// is complete and restarts +//------------------------------------------------- + +template +void fm_operator::start_attack(bool is_restart) +{ + // don't change anything if already in attack state + if (m_env_state == EG_ATTACK) + return; + m_env_state = EG_ATTACK; + + // generally not inverted at start, except if SSG-EG is enabled and + // one of the inverted modes is specified; leave this alone on a + // restart, as it is managed by the clock_ssg_eg_state() code + if (RegisterType::EG_HAS_SSG && !is_restart) + m_ssg_inverted = m_regs.op_ssg_eg_enable(m_opoffs) & bitfield(m_regs.op_ssg_eg_mode(m_opoffs), 2); + + // reset the phase when we start an attack due to a key on + // (but not when due to an SSG-EG restart except in certain cases + // managed directly by the SSG-EG code) + if (!is_restart) + m_phase = 0; + + // if the attack rate >= 62 then immediately go to max attenuation + if (m_cache.eg_rate[EG_ATTACK] >= 62) + m_env_attenuation = 0; +} + + +//------------------------------------------------- +// start_release - start the release phase; +// called when a keyoff happens +//------------------------------------------------- + +template +void fm_operator::start_release() +{ + // don't change anything if already in release state + if (m_env_state >= EG_RELEASE) + return; + m_env_state = EG_RELEASE; + + // if attenuation if inverted due to SSG-EG, snap the inverted attenuation + // as the starting point + if (RegisterType::EG_HAS_SSG && m_ssg_inverted) + { + m_env_attenuation = (0x200 - m_env_attenuation) & 0x3ff; + m_ssg_inverted = false; + } +} + + +//------------------------------------------------- +// clock_keystate - clock the keystate to match +// the incoming keystate +//------------------------------------------------- + +template +void fm_operator::clock_keystate(uint32_t keystate) +{ + assert(keystate == 0 || keystate == 1); + + // has the key changed? + if ((keystate ^ m_key_state) != 0) + { + m_key_state = keystate; + + // if the key has turned on, start the attack + if (keystate != 0) + { + // OPLL has a DP ("depress"?) state to bring the volume + // down before starting the attack + if (RegisterType::EG_HAS_DEPRESS && m_env_attenuation < 0x200) + m_env_state = EG_DEPRESS; + else + start_attack(); + } + + // otherwise, start the release + else + start_release(); + } +} + + +//------------------------------------------------- +// clock_ssg_eg_state - clock the SSG-EG state; +// should only be called if SSG-EG is enabled +//------------------------------------------------- + +template +void fm_operator::clock_ssg_eg_state() +{ + // work only happens once the attenuation crosses above 0x200 + if (!bitfield(m_env_attenuation, 9)) + return; + + // 8 SSG-EG modes: + // 000: repeat normally + // 001: run once, hold low + // 010: repeat, alternating between inverted/non-inverted + // 011: run once, hold high + // 100: inverted repeat normally + // 101: inverted run once, hold low + // 110: inverted repeat, alternating between inverted/non-inverted + // 111: inverted run once, hold high + uint32_t mode = m_regs.op_ssg_eg_mode(m_opoffs); + + // hold modes (1/3/5/7) + if (bitfield(mode, 0)) + { + // set the inverted flag to the end state (0 for modes 1/7, 1 for modes 3/5) + m_ssg_inverted = bitfield(mode, 2) ^ bitfield(mode, 1); + + // if holding, force the attenuation to the expected value once we're + // past the attack phase + if (m_env_state != EG_ATTACK) + m_env_attenuation = m_ssg_inverted ? 0x200 : 0x3ff; + } + + // continuous modes (0/2/4/6) + else + { + // toggle invert in alternating mode (even in attack state) + m_ssg_inverted ^= bitfield(mode, 1); + + // restart attack if in decay/sustain states + if (m_env_state == EG_DECAY || m_env_state == EG_SUSTAIN) + start_attack(true); + + // phase is reset to 0 in modes 0/4 + if (bitfield(mode, 1) == 0) + m_phase = 0; + } + + // in all modes, once we hit release state, attenuation is forced to maximum + if (m_env_state == EG_RELEASE) + m_env_attenuation = 0x3ff; +} + + +//------------------------------------------------- +// clock_envelope - clock the envelope state +// according to the given count +//------------------------------------------------- + +template +void fm_operator::clock_envelope(uint32_t env_counter) +{ + // handle attack->decay transitions + if (m_env_state == EG_ATTACK && m_env_attenuation == 0) + m_env_state = EG_DECAY; + + // handle decay->sustain transitions; it is important to do this immediately + // after the attack->decay transition above in the event that the sustain level + // is set to 0 (in which case we will skip right to sustain without doing any + // decay); as an example where this can be heard, check the cymbals sound + // in channel 0 of shinobi's test mode sound #5 + if (m_env_state == EG_DECAY && m_env_attenuation >= m_cache.eg_sustain) + m_env_state = EG_SUSTAIN; + + // fetch the appropriate 6-bit rate value from the cache + uint32_t rate = m_cache.eg_rate[m_env_state]; + + // compute the rate shift value; this is the shift needed to + // apply to the env_counter such that it becomes a 5.11 fixed + // point number + uint32_t rate_shift = rate >> 2; + env_counter <<= rate_shift; + + // see if the fractional part is 0; if not, it's not time to clock + if (bitfield(env_counter, 0, 11) != 0) + return; + + // determine the increment based on the non-fractional part of env_counter + uint32_t relevant_bits = bitfield(env_counter, (rate_shift <= 11) ? 11 : rate_shift, 3); + uint32_t increment = attenuation_increment(rate, relevant_bits); + + // attack is the only one that increases + if (m_env_state == EG_ATTACK) + { + // glitch means that attack rates of 62/63 don't increment if + // changed after the initial key on (where they are handled + // specially); nukeykt confirms this happens on OPM, OPN, OPL/OPLL + // at least so assuming it is true for everyone + if (rate < 62) + m_env_attenuation += (~m_env_attenuation * increment) >> 4; + } + + // all other cases are similar + else + { + // non-SSG-EG cases just apply the increment + if (!m_regs.op_ssg_eg_enable(m_opoffs)) + m_env_attenuation += increment; + + // SSG-EG only applies if less than mid-point, and then at 4x + else if (m_env_attenuation < 0x200) + m_env_attenuation += 4 * increment; + + // clamp the final attenuation + if (m_env_attenuation >= 0x400) + m_env_attenuation = 0x3ff; + + // transition from depress to attack + if (RegisterType::EG_HAS_DEPRESS && m_env_state == EG_DEPRESS && m_env_attenuation >= 0x200) + start_attack(); + + // transition from release to reverb, should switch at -18dB + if (RegisterType::EG_HAS_REVERB && m_env_state == EG_RELEASE && m_env_attenuation >= 0xc0) + m_env_state = EG_REVERB; + } +} + + +//------------------------------------------------- +// clock_phase - clock the 10.10 phase value; the +// OPN version of the logic has been verified +// against the Nuked phase generator +//------------------------------------------------- + +template +void fm_operator::clock_phase(int32_t lfo_raw_pm) +{ + // read from the cache, or recalculate if PM active + uint32_t phase_step = m_cache.phase_step; + if (phase_step == opdata_cache::PHASE_STEP_DYNAMIC) + phase_step = m_regs.compute_phase_step(m_choffs, m_opoffs, m_cache, lfo_raw_pm); + + // finally apply the step to the current phase value + m_phase += phase_step; +} + + +//------------------------------------------------- +// envelope_attenuation - return the effective +// attenuation of the envelope +//------------------------------------------------- + +template +uint32_t fm_operator::envelope_attenuation(uint32_t am_offset) const +{ + uint32_t result = m_env_attenuation >> m_cache.eg_shift; + + // invert if necessary due to SSG-EG + if (RegisterType::EG_HAS_SSG && m_ssg_inverted) + result = (0x200 - result) & 0x3ff; + + // add in LFO AM modulation + if (m_regs.op_lfo_am_enable(m_opoffs)) + result += am_offset; + + // add in total level and KSL from the cache + result += m_cache.total_level; + + // clamp to max, apply shift, and return + return std::min(result, 0x3ff); +} + + + +//********************************************************* +// FM CHANNEL +//********************************************************* + +//------------------------------------------------- +// fm_channel - constructor +//------------------------------------------------- + +template +fm_channel::fm_channel(fm_engine_base &owner, uint32_t choffs) : + m_choffs(choffs), + m_feedback{ 0, 0 }, + m_feedback_in(0), + m_op{ nullptr, nullptr, nullptr, nullptr }, + m_regs(owner.regs()), + m_owner(owner) +{ +} + + +//------------------------------------------------- +// reset - reset the channel state +//------------------------------------------------- + +template +void fm_channel::reset() +{ + // reset our data + m_feedback[0] = m_feedback[1] = 0; + m_feedback_in = 0; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +template +void fm_channel::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_feedback[0]); + state.save_restore(m_feedback[1]); + state.save_restore(m_feedback_in); +} + + +//------------------------------------------------- +// keyonoff - signal key on/off to our operators +//------------------------------------------------- + +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++) + 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++) + 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()); +} + + +//------------------------------------------------- +// prepare - prepare for clocking +//------------------------------------------------- + +template +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++) + if (m_op[opnum] != nullptr) + if (m_op[opnum]->prepare()) + active_mask |= 1 << opnum; + + return (active_mask != 0); +} + + +//------------------------------------------------- +// clock - master clock of all operators +//------------------------------------------------- + +template +void fm_channel::clock(uint32_t env_counter, int32_t lfo_raw_pm) +{ + // clock the feedback through + m_feedback[0] = m_feedback[1]; + m_feedback[1] = m_feedback_in; + + for (uint32_t opnum = 0; opnum < array_size(m_op); opnum++) + if (m_op[opnum] != nullptr) + m_op[opnum]->clock(env_counter, lfo_raw_pm); + +/* +useful temporary code for envelope debugging +if (m_choffs == 0x101) +{ + for (uint32_t opnum = 0; opnum < array_size(m_op); opnum++) + { + auto &op = *m_op[((opnum & 1) << 1) | ((opnum >> 1) & 1)]; + printf(" %c%03X%c%c ", + "PADSRV"[op.debug_eg_state()], + op.debug_eg_attenuation(), + op.debug_ssg_inverted() ? '-' : '+', + m_regs.op_ssg_eg_enable(op.opoffs()) ? '0' + m_regs.op_ssg_eg_mode(op.opoffs()) : ' '); + } +printf(" -- "); +} +*/ +} + + +//------------------------------------------------- +// output_2op - combine 4 operators according to +// the specified algorithm, returning a sum +// according to the rshift and clipmax parameters, +// which vary between different implementations +//------------------------------------------------- + +template +void fm_channel::output_2op(output_data &output, uint32_t rshift, int32_t clipmax) const +{ + // The first 2 operators should be populated + assert(m_op[0] != nullptr); + assert(m_op[1] != nullptr); + + // AM amount is the same across all operators; compute it once + uint32_t am_offset = m_regs.lfo_am_offset(m_choffs); + + // operator 1 has optional self-feedback + int32_t opmod = 0; + uint32_t feedback = m_regs.ch_feedback(m_choffs); + if (feedback != 0) + opmod = (m_feedback[0] + m_feedback[1]) >> (10 - feedback); + + // compute the 14-bit volume/value of operator 1 and update the feedback + int32_t op1value = m_feedback_in = m_op[0]->compute_volume(m_op[0]->phase() + opmod, am_offset); + + // now that the feedback has been computed, skip the rest if all volumes + // are clear; no need to do all this work for nothing + if (m_regs.ch_output_any(m_choffs) == 0) + return; + + // Algorithms for two-operator case: + // 0: O1 -> O2 -> out + // 1: (O1 + O2) -> out + int32_t result; + if (bitfield(m_regs.ch_algorithm(m_choffs), 0) == 0) + { + // some OPL chips use the previous sample for modulation instead of + // the current sample + opmod = (RegisterType::MODULATOR_DELAY ? m_feedback[1] : op1value) >> 1; + result = m_op[1]->compute_volume(m_op[1]->phase() + opmod, am_offset) >> rshift; + } + else + { + result = (RegisterType::MODULATOR_DELAY ? m_feedback[1] : op1value) >> rshift; + result += m_op[1]->compute_volume(m_op[1]->phase(), am_offset) >> rshift; + int32_t clipmin = -clipmax - 1; + result = clamp(result, clipmin, clipmax); + } + + // add to the output + add_to_output(m_choffs, output, result); +} + + +//------------------------------------------------- +// output_4op - combine 4 operators according to +// the specified algorithm, returning a sum +// according to the rshift and clipmax parameters, +// which vary between different implementations +//------------------------------------------------- + +template +void fm_channel::output_4op(output_data &output, uint32_t rshift, int32_t clipmax) const +{ + // all 4 operators should be populated + assert(m_op[0] != nullptr); + assert(m_op[1] != nullptr); + assert(m_op[2] != nullptr); + assert(m_op[3] != nullptr); + + // AM amount is the same across all operators; compute it once + uint32_t am_offset = m_regs.lfo_am_offset(m_choffs); + + // operator 1 has optional self-feedback + int32_t opmod = 0; + uint32_t feedback = m_regs.ch_feedback(m_choffs); + if (feedback != 0) + opmod = (m_feedback[0] + m_feedback[1]) >> (10 - feedback); + + // compute the 14-bit volume/value of operator 1 and update the feedback + int32_t op1value = m_feedback_in = m_op[0]->compute_volume(m_op[0]->phase() + opmod, am_offset); + + // now that the feedback has been computed, skip the rest if all volumes + // are clear; no need to do all this work for nothing + if (m_regs.ch_output_any(m_choffs) == 0) + return; + + // OPM/OPN offer 8 different connection algorithms for 4 operators, + // and OPL3 offers 4 more, which we designate here as 8-11. + // + // The operators are computed in order, with the inputs pulled from + // an array of values (opout) that is populated as we go: + // 0 = 0 + // 1 = O1 + // 2 = O2 + // 3 = O3 + // 4 = (O4) + // 5 = O1+O2 + // 6 = O1+O3 + // 7 = O2+O3 + // + // The s_algorithm_ops table describes the inputs and outputs of each + // algorithm as follows: + // + // ---------x use opout[x] as operator 2 input + // ------xxx- use opout[x] as operator 3 input + // ---xxx---- use opout[x] as operator 4 input + // --x------- include opout[1] in final sum + // -x-------- include opout[2] in final sum + // x--------- include opout[3] in final sum + #define ALGORITHM(op2in, op3in, op4in, op1out, op2out, op3out) \ + ((op2in) | ((op3in) << 1) | ((op4in) << 4) | ((op1out) << 7) | ((op2out) << 8) | ((op3out) << 9)) + static uint16_t const s_algorithm_ops[8+4] = + { + ALGORITHM(1,2,3, 0,0,0), // 0: O1 -> O2 -> O3 -> O4 -> out (O4) + ALGORITHM(0,5,3, 0,0,0), // 1: (O1 + O2) -> O3 -> O4 -> out (O4) + ALGORITHM(0,2,6, 0,0,0), // 2: (O1 + (O2 -> O3)) -> O4 -> out (O4) + ALGORITHM(1,0,7, 0,0,0), // 3: ((O1 -> O2) + O3) -> O4 -> out (O4) + ALGORITHM(1,0,3, 0,1,0), // 4: ((O1 -> O2) + (O3 -> O4)) -> out (O2+O4) + ALGORITHM(1,1,1, 0,1,1), // 5: ((O1 -> O2) + (O1 -> O3) + (O1 -> O4)) -> out (O2+O3+O4) + ALGORITHM(1,0,0, 0,1,1), // 6: ((O1 -> O2) + O3 + O4) -> out (O2+O3+O4) + ALGORITHM(0,0,0, 1,1,1), // 7: (O1 + O2 + O3 + O4) -> out (O1+O2+O3+O4) + ALGORITHM(1,2,3, 0,0,0), // 8: O1 -> O2 -> O3 -> O4 -> out (O4) [same as 0] + ALGORITHM(0,2,3, 1,0,0), // 9: (O1 + (O2 -> O3 -> O4)) -> out (O1+O4) [unique] + ALGORITHM(1,0,3, 0,1,0), // 10: ((O1 -> O2) + (O3 -> O4)) -> out (O2+O4) [same as 4] + ALGORITHM(0,2,0, 1,0,1) // 11: (O1 + (O2 -> O3) + O4) -> out (O1+O3+O4) [unique] + }; + uint32_t algorithm_ops = s_algorithm_ops[m_regs.ch_algorithm(m_choffs)]; + + // populate the opout table + int16_t opout[8]; + opout[0] = 0; + opout[1] = op1value; + + // compute the 14-bit volume/value of operator 2 + opmod = opout[bitfield(algorithm_ops, 0, 1)] >> 1; + opout[2] = m_op[1]->compute_volume(m_op[1]->phase() + opmod, am_offset); + opout[5] = opout[1] + opout[2]; + + // compute the 14-bit volume/value of operator 3 + opmod = opout[bitfield(algorithm_ops, 1, 3)] >> 1; + opout[3] = m_op[2]->compute_volume(m_op[2]->phase() + opmod, am_offset); + opout[6] = opout[1] + opout[3]; + opout[7] = opout[2] + opout[3]; + + // compute the 14-bit volume/value of operator 4; this could be a noise + // value on the OPM; all algorithms consume OP4 output at a minimum + int32_t result; + if (m_regs.noise_enable() && m_choffs == 7) + result = m_op[3]->compute_noise_volume(am_offset); + else + { + opmod = opout[bitfield(algorithm_ops, 4, 3)] >> 1; + result = m_op[3]->compute_volume(m_op[3]->phase() + opmod, am_offset); + } + result >>= rshift; + + // optionally add OP1, OP2, OP3 + int32_t clipmin = -clipmax - 1; + if (bitfield(algorithm_ops, 7) != 0) + result = clamp(result + (opout[1] >> rshift), clipmin, clipmax); + if (bitfield(algorithm_ops, 8) != 0) + result = clamp(result + (opout[2] >> rshift), clipmin, clipmax); + if (bitfield(algorithm_ops, 9) != 0) + result = clamp(result + (opout[3] >> rshift), clipmin, clipmax); + + // add to the output + add_to_output(m_choffs, output, result); +} + + +//------------------------------------------------- +// output_rhythm_ch6 - special case output +// computation for OPL channel 6 in rhythm mode, +// which outputs a Bass Drum instrument +//------------------------------------------------- + +template +void fm_channel::output_rhythm_ch6(output_data &output, uint32_t rshift, int32_t clipmax) const +{ + // AM amount is the same across all operators; compute it once + uint32_t am_offset = m_regs.lfo_am_offset(m_choffs); + + // Bass Drum: this uses operators 12 and 15 (i.e., channel 6) + // in an almost-normal way, except that if the algorithm is 1, + // the first operator is ignored instead of added in + + // operator 1 has optional self-feedback + int32_t opmod = 0; + uint32_t feedback = m_regs.ch_feedback(m_choffs); + if (feedback != 0) + opmod = (m_feedback[0] + m_feedback[1]) >> (10 - feedback); + + // compute the 14-bit volume/value of operator 1 and update the feedback + int32_t opout1 = m_feedback_in = m_op[0]->compute_volume(m_op[0]->phase() + opmod, am_offset); + + // compute the 14-bit volume/value of operator 2, which is the result + opmod = bitfield(m_regs.ch_algorithm(m_choffs), 0) ? 0 : (opout1 >> 1); + int32_t result = m_op[1]->compute_volume(m_op[1]->phase() + opmod, am_offset) >> rshift; + + // add to the output + add_to_output(m_choffs, output, result * 2); +} + + +//------------------------------------------------- +// output_rhythm_ch7 - special case output +// computation for OPL channel 7 in rhythm mode, +// which outputs High Hat and Snare Drum +// instruments +//------------------------------------------------- + +template +void fm_channel::output_rhythm_ch7(uint32_t phase_select, output_data &output, uint32_t rshift, int32_t clipmax) const +{ + // AM amount is the same across all operators; compute it once + uint32_t am_offset = m_regs.lfo_am_offset(m_choffs); + uint32_t noise_state = bitfield(m_regs.noise_state(), 0); + + // High Hat: this uses the envelope from operator 13 (channel 7), + // and a combination of noise and the operator 13/17 phase select + // to compute the phase + uint32_t phase = (phase_select << 9) | (0xd0 >> (2 * (noise_state ^ phase_select))); + int32_t result = m_op[0]->compute_volume(phase, am_offset) >> rshift; + + // Snare Drum: this uses the envelope from operator 16 (channel 7), + // and a combination of noise and operator 13 phase to pick a phase + uint32_t op13phase = m_op[0]->phase(); + phase = (0x100 << bitfield(op13phase, 8)) ^ (noise_state << 8); + result += m_op[1]->compute_volume(phase, am_offset) >> rshift; + result = clamp(result, -clipmax - 1, clipmax); + + // add to the output + add_to_output(m_choffs, output, result * 2); +} + + +//------------------------------------------------- +// output_rhythm_ch8 - special case output +// computation for OPL channel 8 in rhythm mode, +// which outputs Tom Tom and Top Cymbal instruments +//------------------------------------------------- + +template +void fm_channel::output_rhythm_ch8(uint32_t phase_select, output_data &output, uint32_t rshift, int32_t clipmax) const +{ + // AM amount is the same across all operators; compute it once + uint32_t am_offset = m_regs.lfo_am_offset(m_choffs); + + // Tom Tom: this is just a single operator processed normally + int32_t result = m_op[0]->compute_volume(m_op[0]->phase(), am_offset) >> rshift; + + // Top Cymbal: this uses the envelope from operator 17 (channel 8), + // and the operator 13/17 phase select to compute the phase + uint32_t phase = 0x100 | (phase_select << 9); + result += m_op[1]->compute_volume(phase, am_offset) >> rshift; + result = clamp(result, -clipmax - 1, clipmax); + + // add to the output + add_to_output(m_choffs, output, result * 2); +} + + + +//********************************************************* +// FM ENGINE BASE +//********************************************************* + +//------------------------------------------------- +// fm_engine_base - constructor +//------------------------------------------------- + +template +fm_engine_base::fm_engine_base(ymfm_interface &intf) : + m_intf(intf), + m_env_counter(0), + m_status(0), + m_clock_prescale(RegisterType::DEFAULT_PRESCALE), + m_irq_mask(STATUS_TIMERA | STATUS_TIMERB), + m_irq_state(0), + m_timer_running{0,0}, + m_active_channels(ALL_CHANNELS), + m_modified_channels(ALL_CHANNELS), + m_prepare_count(0) +{ + // inform the interface of their engine + m_intf.m_engine = this; + + // create the channels + for (uint32_t chnum = 0; chnum < CHANNELS; chnum++) + m_channel[chnum] = std::make_unique>(*this, RegisterType::channel_offset(chnum)); + + // create the operators + for (uint32_t opnum = 0; opnum < OPERATORS; opnum++) + m_operator[opnum] = std::make_unique>(*this, RegisterType::operator_offset(opnum)); + +#if (DEBUG_LOG_WAVFILES) + for (uint32_t chnum = 0; chnum < CHANNELS; chnum++) + m_wavfile[chnum].set_index(chnum); +#endif + + // do the initial operator assignment + assign_operators(); +} + + +//------------------------------------------------- +// reset - reset the overall state +//------------------------------------------------- + +template +void fm_engine_base::reset() +{ + // reset all status bits + set_reset_status(0, 0xff); + + // register type-specific initialization + m_regs.reset(); + + // explicitly write to the mode register since it has side-effects + // QUESTION: old cores initialize this to 0x30 -- who is right? + write(RegisterType::REG_MODE, 0); + + // reset the channels + for (auto &chan : m_channel) + chan->reset(); + + // reset the operators + for (auto &op : m_operator) + op->reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +template +void fm_engine_base::save_restore(ymfm_saved_state &state) +{ + // save our data + state.save_restore(m_env_counter); + state.save_restore(m_status); + state.save_restore(m_clock_prescale); + state.save_restore(m_irq_mask); + state.save_restore(m_irq_state); + state.save_restore(m_timer_running[0]); + state.save_restore(m_timer_running[1]); + state.save_restore(m_total_clocks); + + // save the register/family data + m_regs.save_restore(state); + + // save channel data + for (uint32_t chnum = 0; chnum < CHANNELS; chnum++) + m_channel[chnum]->save_restore(state); + + // save operator data + for (uint32_t opnum = 0; opnum < OPERATORS; opnum++) + m_operator[opnum]->save_restore(state); + + // invalidate any caches + invalidate_caches(); +} + + +//------------------------------------------------- +// clock - iterate over all channels, clocking +// them forward one step +//------------------------------------------------- + +template +uint32_t fm_engine_base::clock(uint32_t chanmask) +{ + // update the clock counter + m_total_clocks++; + + // if something was modified, prepare + // also prepare every 4k samples to catch ending notes + if (m_modified_channels != 0 || m_prepare_count++ >= 4096) + { + // reassign operators to channels if dynamic + if (RegisterType::DYNAMIC_OPS) + assign_operators(); + + // call each channel to prepare + m_active_channels = 0; + for (uint32_t chnum = 0; chnum < CHANNELS; chnum++) + if (bitfield(chanmask, chnum)) + if (m_channel[chnum]->prepare()) + m_active_channels |= 1 << chnum; + + // reset the modified channels and prepare count + m_modified_channels = m_prepare_count = 0; + } + + // if the envelope clock divider is 1, just increment by 4; + // otherwise, increment by 1 and manually wrap when we reach the divide count + if (RegisterType::EG_CLOCK_DIVIDER == 1) + m_env_counter += 4; + else if (bitfield(++m_env_counter, 0, 2) == RegisterType::EG_CLOCK_DIVIDER) + m_env_counter += 4 - RegisterType::EG_CLOCK_DIVIDER; + + // clock the noise generator + int32_t lfo_raw_pm = m_regs.clock_noise_and_lfo(); + + // now update the state of all the channels and operators + for (uint32_t chnum = 0; chnum < CHANNELS; chnum++) + if (bitfield(chanmask, chnum)) + m_channel[chnum]->clock(m_env_counter, lfo_raw_pm); + + // return the envelope counter as it is used to clock ADPCM-A + return m_env_counter; +} + + +//------------------------------------------------- +// output - compute a sum over the relevant +// channels +//------------------------------------------------- + +template +void fm_engine_base::output(output_data &output, uint32_t rshift, int32_t clipmax, uint32_t chanmask) const +{ + // mask out some channels for debug purposes + chanmask &= debug::GLOBAL_FM_CHANNEL_MASK; + + // mask out inactive channels + if (!DEBUG_LOG_WAVFILES) + chanmask &= m_active_channels; + + // handle the rhythm case, where some of the operators are dedicated + // to percussion (this is an OPL-specific feature) + if (m_regs.rhythm_enable()) + { + // we don't support the OPM noise channel here; ensure it is off + assert(m_regs.noise_enable() == 0); + + // precompute the operator 13+17 phase selection value + uint32_t op13phase = m_operator[13]->phase(); + uint32_t op17phase = m_operator[17]->phase(); + uint32_t phase_select = (bitfield(op13phase, 2) ^ bitfield(op13phase, 7)) | bitfield(op13phase, 3) | (bitfield(op17phase, 5) ^ bitfield(op17phase, 3)); + + // sum over all the desired channels + for (uint32_t chnum = 0; chnum < CHANNELS; chnum++) + if (bitfield(chanmask, chnum)) + { +#if (DEBUG_LOG_WAVFILES) + auto reference = output; +#endif + if (chnum == 6) + m_channel[chnum]->output_rhythm_ch6(output, rshift, clipmax); + else if (chnum == 7) + m_channel[chnum]->output_rhythm_ch7(phase_select, output, rshift, clipmax); + else if (chnum == 8) + m_channel[chnum]->output_rhythm_ch8(phase_select, output, rshift, clipmax); + else if (m_channel[chnum]->is4op()) + m_channel[chnum]->output_4op(output, rshift, clipmax); + else + m_channel[chnum]->output_2op(output, rshift, clipmax); +#if (DEBUG_LOG_WAVFILES) + m_wavfile[chnum].add(output, reference); +#endif + } + } + else + { + // sum over all the desired channels + for (uint32_t chnum = 0; chnum < CHANNELS; chnum++) + if (bitfield(chanmask, chnum)) + { +#if (DEBUG_LOG_WAVFILES) + auto reference = output; +#endif + if (m_channel[chnum]->is4op()) + m_channel[chnum]->output_4op(output, rshift, clipmax); + else + m_channel[chnum]->output_2op(output, rshift, clipmax); +#if (DEBUG_LOG_WAVFILES) + m_wavfile[chnum].add(output, reference); +#endif + } + } +} + + +//------------------------------------------------- +// write - handle writes to the OPN registers +//------------------------------------------------- + +template +void fm_engine_base::write(uint16_t regnum, uint8_t data) +{ + debug::log_fm_write("%03X = %02X\n", regnum, data); + + // special case: writes to the mode register can impact IRQs; + // schedule these writes to ensure ordering with timers + if (regnum == RegisterType::REG_MODE) + { + m_intf.ymfm_sync_mode_write(data); + return; + } + + // for now just mark all channels as modified + m_modified_channels = ALL_CHANNELS; + + // most writes are passive, consumed only when needed + uint32_t keyon_channel; + uint32_t keyon_opmask; + if (m_regs.write(regnum, data, keyon_channel, keyon_opmask)) + { + // handle writes to the keyon register(s) + if (keyon_channel < CHANNELS) + { + // normal channel on/off + m_channel[keyon_channel]->keyonoff(keyon_opmask, KEYON_NORMAL, keyon_channel); + } + else if (CHANNELS >= 9 && keyon_channel == RegisterType::RHYTHM_CHANNEL) + { + // special case for the OPL rhythm channels + m_channel[6]->keyonoff(bitfield(keyon_opmask, 4) ? 3 : 0, KEYON_RHYTHM, 6); + m_channel[7]->keyonoff(bitfield(keyon_opmask, 0) | (bitfield(keyon_opmask, 3) << 1), KEYON_RHYTHM, 7); + m_channel[8]->keyonoff(bitfield(keyon_opmask, 2) | (bitfield(keyon_opmask, 1) << 1), KEYON_RHYTHM, 8); + } + } +} + + +//------------------------------------------------- +// status - return the current state of the +// status flags +//------------------------------------------------- + +template +uint8_t fm_engine_base::status() const +{ + return m_status & ~STATUS_BUSY & ~m_regs.status_mask(); +} + + +//------------------------------------------------- +// assign_operators - get the current mapping of +// operators to channels and assign them all +//------------------------------------------------- + +template +void fm_engine_base::assign_operators() +{ + typename RegisterType::operator_mapping map; + m_regs.operator_map(map); + + for (uint32_t chnum = 0; chnum < CHANNELS; chnum++) + for (uint32_t index = 0; index < 4; index++) + { + uint32_t opnum = bitfield(map.chan[chnum], 8 * index, 8); + m_channel[chnum]->assign(index, (opnum == 0xff) ? nullptr : m_operator[opnum].get()); + } +} + + +//------------------------------------------------- +// update_timer - update the state of the given +// timer +//------------------------------------------------- + +template +void fm_engine_base::update_timer(uint32_t tnum, uint32_t enable, int32_t delta_clocks) +{ + // 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 - 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; + + // reset it + m_intf.ymfm_set_timer(tnum, period * OPERATORS * m_clock_prescale); + m_timer_running[tnum] = 1; + } + + // if the timer is not live, ensure it is not enabled + else if (!enable) + { + m_intf.ymfm_set_timer(tnum, -1); + m_timer_running[tnum] = 0; + } +} + + +//------------------------------------------------- +// engine_timer_expired - timer has expired - signal +// status and possibly IRQs +//------------------------------------------------- + +template +void fm_engine_base::engine_timer_expired(uint32_t tnum) +{ + // update status + if (tnum == 0 && m_regs.enable_timer_a()) + set_reset_status(STATUS_TIMERA, 0); + else if (tnum == 1 && m_regs.enable_timer_b()) + set_reset_status(STATUS_TIMERB, 0); + + // if timer A fired in CSM mode, trigger CSM on all relevant channels + if (tnum == 0 && m_regs.csm()) + for (uint32_t chnum = 0; chnum < CHANNELS; chnum++) + if (bitfield(RegisterType::CSM_TRIGGER_MASK, chnum)) + { + m_channel[chnum]->keyonoff(1, KEYON_CSM, chnum); + m_modified_channels |= 1 << chnum; + } + + // reset + m_timer_running[tnum] = false; + update_timer(tnum, 1, 0); +} + + +//------------------------------------------------- +// check_interrupts - check the interrupt sources +// for interrupts +//------------------------------------------------- + +template +void fm_engine_base::engine_check_interrupts() +{ + // update the state + uint8_t old_state = m_irq_state; + m_irq_state = ((m_status & m_irq_mask & ~m_regs.status_mask()) != 0); + + // set the IRQ status bit + if (m_irq_state) + m_status |= STATUS_IRQ; + else + m_status &= ~STATUS_IRQ; + + // if changed, signal the new state + if (old_state != m_irq_state) + m_intf.ymfm_update_irq(m_irq_state ? true : false); +} + + +//------------------------------------------------- +// engine_mode_write - handle a mode register write +// via timer callback +//------------------------------------------------- + +template +void fm_engine_base::engine_mode_write(uint8_t data) +{ + // mark all channels as modified + m_modified_channels = ALL_CHANNELS; + + // actually write the mode register now + uint32_t dummy1, dummy2; + m_regs.write(RegisterType::REG_MODE, data, dummy1, dummy2); + + // reset IRQ status -- when written, all other bits are ignored + // QUESTION: should this maybe just reset the IRQ bit and not all the bits? + // That is, check_interrupts would only set, this would only clear? + if (m_regs.irq_reset()) + set_reset_status(0, 0x78); + else + { + // reset timer status + uint8_t reset_mask = 0; + if (m_regs.reset_timer_b()) + reset_mask |= RegisterType::STATUS_TIMERB; + if (m_regs.reset_timer_a()) + reset_mask |= RegisterType::STATUS_TIMERA; + set_reset_status(0, reset_mask); + + // load timers; note that timer B gets a small negative adjustment because + // the *16 multiplier is free-running, so the first tick of the clock + // is a bit shorter + update_timer(1, m_regs.load_timer_b(), -(m_total_clocks & 15)); + update_timer(0, m_regs.load_timer_a(), 0); + } +} + +} diff --git a/src/sound/ymfm/ymfm_misc.cpp b/src/sound/ymfm/ymfm_misc.cpp new file mode 100644 index 000000000..fd0575f55 --- /dev/null +++ b/src/sound/ymfm/ymfm_misc.cpp @@ -0,0 +1,175 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "ymfm_misc.h" + +namespace ymfm +{ + +//********************************************************* +// YM2149 +//********************************************************* + +//------------------------------------------------- +// ym2149 - constructor +//------------------------------------------------- + +ym2149::ym2149(ymfm_interface &intf) : + m_address(0), + m_ssg(intf) +{ +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ym2149::reset() +{ + // reset the engines + m_ssg.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ym2149::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + m_ssg.save_restore(state); +} + + +//------------------------------------------------- +// read_data - read the data register +//------------------------------------------------- + +uint8_t ym2149::read_data() +{ + return m_ssg.read(m_address & 0x0f); +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ym2149::read(uint32_t offset) +{ + uint8_t result = 0xff; + switch (offset & 3) // BC2,BC1 + { + case 0: // inactive + break; + + case 1: // address + break; + + case 2: // inactive + break; + + case 3: // read + result = read_data(); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ym2149::write_address(uint8_t data) +{ + // just set the address + m_address = data; +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym2149::write_data(uint8_t data) +{ + m_ssg.write(m_address & 0x0f, data); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym2149::write(uint32_t offset, uint8_t data) +{ + switch (offset & 3) // BC2,BC1 + { + case 0: // address + write_address(data); + break; + + case 1: // inactive + break; + + case 2: // write + write_data(data); + break; + + case 3: // address + write_address(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate samples of SSG sound +//------------------------------------------------- + +void ym2149::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the SSG + m_ssg.clock(); + + // YM2149 keeps the three SSG outputs independent + m_ssg.output(*output); + } +} + +} diff --git a/src/sound/ymfm/ymfm_misc.h b/src/sound/ymfm/ymfm_misc.h new file mode 100644 index 000000000..628d128f6 --- /dev/null +++ b/src/sound/ymfm/ymfm_misc.h @@ -0,0 +1,93 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_MISC_H +#define YMFM_MISC_H + +#pragma once + +#include "ymfm.h" +#include "ymfm_adpcm.h" +#include "ymfm_ssg.h" + +namespace ymfm +{ + +//********************************************************* +// SSG IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ym2149 + +// ym2149 is just an SSG with no FM part, but we expose FM-like parts so that it +// integrates smoothly with everything else; they just don't do anything +class ym2149 +{ +public: + static constexpr uint32_t OUTPUTS = ssg_engine::OUTPUTS; + static constexpr uint32_t SSG_OUTPUTS = ssg_engine::OUTPUTS; + using output_data = ymfm_output; + + // constructor + ym2149(ymfm_interface &intf); + + // configuration + void ssg_override(ssg_override &intf) { m_ssg.override(intf); } + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return input_clock / ssg_engine::CLOCK_DIVIDER / 8; } + + // read access + uint8_t read_data(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal state + uint8_t m_address; // address register + ssg_engine m_ssg; // SSG engine +}; + +} + +#endif // YMFM_MISC_H diff --git a/src/sound/ymfm/ymfm_opl.cpp b/src/sound/ymfm/ymfm_opl.cpp new file mode 100644 index 000000000..86215c5b2 --- /dev/null +++ b/src/sound/ymfm/ymfm_opl.cpp @@ -0,0 +1,2209 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "ymfm_opl.h" +#include "ymfm_fm.ipp" + +namespace ymfm +{ + +//------------------------------------------------- +// opl_key_scale_atten - converts an +// OPL concatenated block (3 bits) and fnum +// (10 bits) into an attenuation offset; values +// here are for 6dB/octave, in 0.75dB units +// (matching total level LSB) +//------------------------------------------------- + +inline uint32_t opl_key_scale_atten(uint32_t block, uint32_t fnum_4msb) +{ + // this table uses the top 4 bits of FNUM and are the maximal values + // (for when block == 7). Values for other blocks can be computed by + // subtracting 8 for each block below 7. + static uint8_t const fnum_to_atten[16] = { 0,24,32,37,40,43,45,47,48,50,51,52,53,54,55,56 }; + int32_t result = fnum_to_atten[fnum_4msb] - 8 * (block ^ 7); + return std::max(0, result); +} + + +//********************************************************* +// OPL REGISTERS +//********************************************************* + +//------------------------------------------------- +// opl_registers_base - constructor +//------------------------------------------------- + +template +opl_registers_base::opl_registers_base() : + m_lfo_am_counter(0), + m_lfo_pm_counter(0), + m_noise_lfsr(1), + m_lfo_am(0) +{ + // create these pointers to appease overzealous compilers checking array + // bounds in unreachable code (looking at you, clang) + uint16_t *wf0 = &m_waveform[0][0]; + uint16_t *wf1 = &m_waveform[1 % WAVEFORMS][0]; + uint16_t *wf2 = &m_waveform[2 % WAVEFORMS][0]; + uint16_t *wf3 = &m_waveform[3 % WAVEFORMS][0]; + uint16_t *wf4 = &m_waveform[4 % WAVEFORMS][0]; + uint16_t *wf5 = &m_waveform[5 % WAVEFORMS][0]; + uint16_t *wf6 = &m_waveform[6 % WAVEFORMS][0]; + uint16_t *wf7 = &m_waveform[7 % WAVEFORMS][0]; + + // create the waveforms + for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) + wf0[index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15); + + if (WAVEFORMS >= 4) + { + uint16_t zeroval = wf0[0]; + for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) + { + wf1[index] = bitfield(index, 9) ? zeroval : wf0[index]; + wf2[index] = wf0[index] & 0x7fff; + wf3[index] = bitfield(index, 8) ? zeroval : (wf0[index] & 0x7fff); + if (WAVEFORMS >= 8) + { + wf4[index] = bitfield(index, 9) ? zeroval : wf0[index * 2]; + wf5[index] = bitfield(index, 9) ? zeroval : wf0[(index * 2) & 0x1ff]; + wf6[index] = bitfield(index, 9) << 15; + wf7[index] = (bitfield(index, 9) ? (index ^ 0x13ff) : index) << 3; + } + } + } +} + + +//------------------------------------------------- +// reset - reset to initial state +//------------------------------------------------- + +template +void opl_registers_base::reset() +{ + std::fill_n(&m_regdata[0], REGISTERS, 0); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +template +void opl_registers_base::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_lfo_am_counter); + state.save_restore(m_lfo_pm_counter); + state.save_restore(m_lfo_am); + state.save_restore(m_noise_lfsr); + state.save_restore(m_regdata); +} + + +//------------------------------------------------- +// operator_map - return an array of operator +// indices for each channel; for OPL this is fixed +//------------------------------------------------- + +template +void opl_registers_base::operator_map(operator_mapping &dest) const +{ + if (Revision <= 2) + { + // OPL/OPL2 has a fixed map, all 2 operators + static const operator_mapping s_fixed_map = + { { + operator_list( 0, 3 ), // Channel 0 operators + operator_list( 1, 4 ), // Channel 1 operators + operator_list( 2, 5 ), // Channel 2 operators + operator_list( 6, 9 ), // Channel 3 operators + operator_list( 7, 10 ), // Channel 4 operators + operator_list( 8, 11 ), // Channel 5 operators + operator_list( 12, 15 ), // Channel 6 operators + operator_list( 13, 16 ), // Channel 7 operators + operator_list( 14, 17 ), // Channel 8 operators + } }; + dest = s_fixed_map; + } + else + { + // OPL3/OPL4 can be configured for 2 or 4 operators + uint32_t fourop = fourop_enable(); + + dest.chan[ 0] = bitfield(fourop, 0) ? operator_list( 0, 3, 6, 9 ) : operator_list( 0, 3 ); + dest.chan[ 1] = bitfield(fourop, 1) ? operator_list( 1, 4, 7, 10 ) : operator_list( 1, 4 ); + dest.chan[ 2] = bitfield(fourop, 2) ? operator_list( 2, 5, 8, 11 ) : operator_list( 2, 5 ); + dest.chan[ 3] = bitfield(fourop, 0) ? operator_list() : operator_list( 6, 9 ); + dest.chan[ 4] = bitfield(fourop, 1) ? operator_list() : operator_list( 7, 10 ); + dest.chan[ 5] = bitfield(fourop, 2) ? operator_list() : operator_list( 8, 11 ); + dest.chan[ 6] = operator_list( 12, 15 ); + dest.chan[ 7] = operator_list( 13, 16 ); + dest.chan[ 8] = operator_list( 14, 17 ); + + dest.chan[ 9] = bitfield(fourop, 3) ? operator_list( 18, 21, 24, 27 ) : operator_list( 18, 21 ); + dest.chan[10] = bitfield(fourop, 4) ? operator_list( 19, 22, 25, 28 ) : operator_list( 19, 22 ); + dest.chan[11] = bitfield(fourop, 5) ? operator_list( 20, 23, 26, 29 ) : operator_list( 20, 23 ); + dest.chan[12] = bitfield(fourop, 3) ? operator_list() : operator_list( 24, 27 ); + dest.chan[13] = bitfield(fourop, 4) ? operator_list() : operator_list( 25, 28 ); + dest.chan[14] = bitfield(fourop, 5) ? operator_list() : operator_list( 26, 29 ); + dest.chan[15] = operator_list( 30, 33 ); + dest.chan[16] = operator_list( 31, 34 ); + dest.chan[17] = operator_list( 32, 35 ); + } +} + + +//------------------------------------------------- +// write - handle writes to the register array +//------------------------------------------------- + +template +bool opl_registers_base::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask) +{ + assert(index < REGISTERS); + + // writes to the mode register with high bit set ignore the low bits + if (index == REG_MODE && bitfield(data, 7) != 0) + m_regdata[index] |= 0x80; + else + m_regdata[index] = data; + + // handle writes to the rhythm keyons + if (index == 0xbd) + { + channel = RHYTHM_CHANNEL; + opmask = bitfield(data, 5) ? bitfield(data, 0, 5) : 0; + return true; + } + + // handle writes to the channel keyons + if ((index & 0xf0) == 0xb0) + { + channel = index & 0x0f; + if (channel < 9) + { + if (IsOpl3Plus) + channel += 9 * bitfield(index, 8); + opmask = bitfield(data, 5) ? 15 : 0; + return true; + } + } + return false; +} + + +//------------------------------------------------- +// clock_noise_and_lfo - clock the noise and LFO, +// handling clock division, depth, and waveform +// computations +//------------------------------------------------- + +static int32_t opl_clock_noise_and_lfo(uint32_t &noise_lfsr, uint16_t &lfo_am_counter, uint16_t &lfo_pm_counter, uint8_t &lfo_am, uint32_t am_depth, uint32_t pm_depth) +{ + // OPL has a 23-bit noise generator for the rhythm section, running at + // a constant rate, used only for percussion input + noise_lfsr <<= 1; + noise_lfsr |= bitfield(noise_lfsr, 23) ^ bitfield(noise_lfsr, 9) ^ bitfield(noise_lfsr, 8) ^ bitfield(noise_lfsr, 1); + + // OPL has two fixed-frequency LFOs, one for AM, one for PM + + // the AM LFO has 210*64 steps; at a nominal 50kHz output, + // this equates to a period of 50000/(210*64) = 3.72Hz + uint32_t am_counter = lfo_am_counter++; + if (am_counter >= 210*64 - 1) + lfo_am_counter = 0; + + // low 8 bits are fractional; depth 0 is divided by 2, while depth 1 is times 2 + int shift = 9 - 2 * am_depth; + + // AM value is the upper bits of the value, inverted across the midpoint + // to produce a triangle + lfo_am = ((am_counter < 105*64) ? am_counter : (210*64+63 - am_counter)) >> shift; + + // the PM LFO has 8192 steps, or a nominal period of 6.1Hz + uint32_t pm_counter = lfo_pm_counter++; + + // PM LFO is broken into 8 chunks, each lasting 1024 steps; the PM value + // depends on the upper bits of FNUM, so this value is a fraction and + // sign to apply to that value, as a 1.3 value + static int8_t const pm_scale[8] = { 8, 4, 0, -4, -8, -4, 0, 4 }; + return pm_scale[bitfield(pm_counter, 10, 3)] >> (pm_depth ^ 1); +} + +template +int32_t opl_registers_base::clock_noise_and_lfo() +{ + return opl_clock_noise_and_lfo(m_noise_lfsr, m_lfo_am_counter, m_lfo_pm_counter, m_lfo_am, lfo_am_depth(), lfo_pm_depth()); +} + + +//------------------------------------------------- +// cache_operator_data - fill the operator cache +// with prefetched data; note that this code is +// also used by ymopna_registers, so it must +// handle upper channels cleanly +//------------------------------------------------- + +template +void opl_registers_base::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache) +{ + // set up the easy stuff + cache.waveform = &m_waveform[op_waveform(opoffs) % WAVEFORMS][0]; + + // get frequency from the channel + uint32_t block_freq = cache.block_freq = ch_block_freq(choffs); + + // compute the keycode: block_freq is: + // + // 111 | + // 21098|76543210 + // BBBFF|FFFFFFFF + // ^^^?? + // + // the 4-bit keycode uses the top 3 bits plus one of the next two bits + uint32_t keycode = bitfield(block_freq, 10, 3) << 1; + + // lowest bit is determined by note_select(); note that it is + // actually reversed from what the manual says, however + keycode |= bitfield(block_freq, 9 - note_select(), 1); + + // no detune adjustment on OPL + cache.detune = 0; + + // multiple value, as an x.1 value (0 means 0.5) + // replace the low bit with a table lookup to give 0,1,2,3,4,5,6,7,8,9,10,10,12,12,15,15 + uint32_t multiple = op_multiple(opoffs); + cache.multiple = ((multiple & 0xe) | bitfield(0xc2aa, multiple)) * 2; + if (cache.multiple == 0) + cache.multiple = 1; + + // phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on block_freq, detune, + // and multiple, so compute it after we've done those + if (op_lfo_pm_enable(opoffs) == 0) + cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0); + else + cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC; + + // total level, scaled by 8 + cache.total_level = op_total_level(opoffs) << 3; + + // pre-add key scale level + uint32_t ksl = op_ksl(opoffs); + if (ksl != 0) + cache.total_level += opl_key_scale_atten(bitfield(block_freq, 10, 3), bitfield(block_freq, 6, 4)) << ksl; + + // 4-bit sustain level, but 15 means 31 so effectively 5 bits + cache.eg_sustain = op_sustain_level(opoffs); + cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10; + cache.eg_sustain <<= 5; + + // determine KSR adjustment for enevlope rates + uint32_t ksrval = keycode >> (2 * (op_ksr(opoffs) ^ 1)); + cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 4, ksrval); + cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 4, ksrval); + cache.eg_rate[EG_SUSTAIN] = op_eg_sustain(opoffs) ? 0 : effective_rate(op_release_rate(opoffs) * 4, ksrval); + cache.eg_rate[EG_RELEASE] = effective_rate(op_release_rate(opoffs) * 4, ksrval); + cache.eg_rate[EG_DEPRESS] = 0x3f; +} + + +//------------------------------------------------- +// compute_phase_step - compute the phase step +//------------------------------------------------- + +static uint32_t opl_compute_phase_step(uint32_t block_freq, uint32_t multiple, int32_t lfo_raw_pm) +{ + // OPL phase calculation has no detuning, but uses FNUMs like + // the OPN version, and computes PM a bit differently + + // extract frequency number as a 12-bit fraction + uint32_t fnum = bitfield(block_freq, 0, 10) << 2; + + // apply the phase adjustment based on the upper 3 bits + // of FNUM and the PM depth parameters + fnum += (lfo_raw_pm * bitfield(block_freq, 7, 3)) >> 1; + + // keep fnum to 12 bits + fnum &= 0xfff; + + // apply block shift to compute phase step + uint32_t block = bitfield(block_freq, 10, 3); + uint32_t phase_step = (fnum << block) >> 2; + + // apply frequency multiplier (which is cached as an x.1 value) + return (phase_step * multiple) >> 1; +} + +template +uint32_t opl_registers_base::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm) +{ + return opl_compute_phase_step(cache.block_freq, cache.multiple, op_lfo_pm_enable(opoffs) ? lfo_raw_pm : 0); +} + + +//------------------------------------------------- +// log_keyon - log a key-on event +//------------------------------------------------- + +template +std::string opl_registers_base::log_keyon(uint32_t choffs, uint32_t opoffs) +{ + uint32_t chnum = (choffs & 15) + 9 * bitfield(choffs, 8); + uint32_t opnum = (opoffs & 31) - 2 * ((opoffs & 31) / 8) + 18 * bitfield(opoffs, 8); + + char buffer[256]; + char *end = &buffer[0]; + + end += sprintf(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), + ch_algorithm(choffs), + op_multiple(opoffs), + op_total_level(opoffs), + op_ksr(opoffs), + note_select(), + op_ksl(opoffs), + op_attack_rate(opoffs), + op_decay_rate(opoffs), + op_release_rate(opoffs), + op_sustain_level(opoffs), + op_eg_sustain(opoffs)); + + if (OUTPUTS > 1) + end += sprintf(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 += sprintf(end, " am=%u", lfo_am_depth()); + if (op_lfo_pm_enable(opoffs) != 0) + end += sprintf(end, " pm=%u", lfo_pm_depth()); + if (waveform_enable() && op_waveform(opoffs) != 0) + end += sprintf(end, " wf=%u", op_waveform(opoffs)); + if (is_rhythm(choffs)) + end += sprintf(end, " rhy=1"); + if (DYNAMIC_OPS) + { + operator_mapping map; + operator_map(map); + if (bitfield(map.chan[chnum], 16, 8) != 0xff) + end += sprintf(end, " 4op"); + } + + return buffer; +} + + +//********************************************************* +// OPLL SPECIFICS +//********************************************************* + +//------------------------------------------------- +// opll_registers - constructor +//------------------------------------------------- + +opll_registers::opll_registers() : + m_lfo_am_counter(0), + m_lfo_pm_counter(0), + m_noise_lfsr(1), + m_lfo_am(0) +{ + // create the waveforms + for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) + m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15); + + uint16_t zeroval = m_waveform[0][0]; + for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) + m_waveform[1][index] = bitfield(index, 9) ? zeroval : m_waveform[0][index]; + + // initialize the instruments to something sane + for (uint32_t choffs = 0; choffs < CHANNELS; choffs++) + m_chinst[choffs] = &m_regdata[0]; + for (uint32_t opoffs = 0; opoffs < OPERATORS; opoffs++) + m_opinst[opoffs] = &m_regdata[bitfield(opoffs, 0)]; +} + + +//------------------------------------------------- +// reset - reset to initial state +//------------------------------------------------- + +void opll_registers::reset() +{ + std::fill_n(&m_regdata[0], REGISTERS, 0); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void opll_registers::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_lfo_am_counter); + state.save_restore(m_lfo_pm_counter); + state.save_restore(m_lfo_am); + state.save_restore(m_noise_lfsr); + state.save_restore(m_regdata); +} + + +//------------------------------------------------- +// operator_map - return an array of operator +// indices for each channel; for OPLL this is fixed +//------------------------------------------------- + +void opll_registers::operator_map(operator_mapping &dest) const +{ + static const operator_mapping s_fixed_map = + { { + operator_list( 0, 1 ), // Channel 0 operators + operator_list( 2, 3 ), // Channel 1 operators + operator_list( 4, 5 ), // Channel 2 operators + operator_list( 6, 7 ), // Channel 3 operators + operator_list( 8, 9 ), // Channel 4 operators + operator_list( 10, 11 ), // Channel 5 operators + operator_list( 12, 13 ), // Channel 6 operators + operator_list( 14, 15 ), // Channel 7 operators + operator_list( 16, 17 ), // Channel 8 operators + } }; + dest = s_fixed_map; +} + + +//------------------------------------------------- +// write - handle writes to the register array; +// note that this code is also used by +// ymopl3_registers, so it must handle upper +// channels cleanly +//------------------------------------------------- + +bool opll_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask) +{ + // unclear the address is masked down to 6 bits or if writes above + // the register top are ignored; assuming the latter for now + if (index >= REGISTERS) + return false; + + // write the new data + m_regdata[index] = data; + + // handle writes to the rhythm keyons + if (index == 0x0e) + { + channel = RHYTHM_CHANNEL; + opmask = bitfield(data, 5) ? bitfield(data, 0, 5) : 0; + return true; + } + + // handle writes to the channel keyons + if ((index & 0xf0) == 0x20) + { + channel = index & 0x0f; + if (channel < CHANNELS) + { + opmask = bitfield(data, 4) ? 3 : 0; + return true; + } + } + return false; +} + + +//------------------------------------------------- +// clock_noise_and_lfo - clock the noise and LFO, +// handling clock division, depth, and waveform +// computations +//------------------------------------------------- + +int32_t opll_registers::clock_noise_and_lfo() +{ + // implementation is the same as OPL with fixed depths + return opl_clock_noise_and_lfo(m_noise_lfsr, m_lfo_am_counter, m_lfo_pm_counter, m_lfo_am, 1, 1); +} + + +//------------------------------------------------- +// cache_operator_data - fill the operator cache +// with prefetched data; note that this code is +// also used by ymopna_registers, so it must +// handle upper channels cleanly +//------------------------------------------------- + +void opll_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache) +{ + // first set up the instrument data + uint32_t instrument = ch_instrument(choffs); + if (rhythm_enable() && choffs >= 6) + m_chinst[choffs] = &m_instdata[8 * (15 + (choffs - 6))]; + else + m_chinst[choffs] = (instrument == 0) ? &m_regdata[0] : &m_instdata[8 * (instrument - 1)]; + m_opinst[opoffs] = m_chinst[choffs] + bitfield(opoffs, 0); + + // set up the easy stuff + cache.waveform = &m_waveform[op_waveform(opoffs) % WAVEFORMS][0]; + + // get frequency from the channel + uint32_t block_freq = cache.block_freq = ch_block_freq(choffs); + + // compute the keycode: block_freq is: + // + // 11 | + // 1098|76543210 + // BBBF|FFFFFFFF + // ^^^^ + // + // the 4-bit keycode uses the top 4 bits + uint32_t keycode = bitfield(block_freq, 8, 4); + + // no detune adjustment on OPLL + cache.detune = 0; + + // multiple value, as an x.1 value (0 means 0.5) + // replace the low bit with a table lookup to give 0,1,2,3,4,5,6,7,8,9,10,10,12,12,15,15 + uint32_t multiple = op_multiple(opoffs); + cache.multiple = ((multiple & 0xe) | bitfield(0xc2aa, multiple)) * 2; + if (cache.multiple == 0) + cache.multiple = 1; + + // phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on + // block_freq, detune, and multiple, so compute it after we've done those + if (op_lfo_pm_enable(opoffs) == 0) + cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0); + else + cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC; + + // total level, scaled by 8; for non-rhythm operator 0, this is the total + // level from the instrument data; for other operators it is 4*volume + if (bitfield(opoffs, 0) == 1 || (rhythm_enable() && choffs >= 7)) + cache.total_level = op_volume(opoffs) * 4; + else + cache.total_level = ch_total_level(choffs); + cache.total_level <<= 3; + + // pre-add key scale level + uint32_t ksl = op_ksl(opoffs); + if (ksl != 0) + cache.total_level += opl_key_scale_atten(bitfield(block_freq, 9, 3), bitfield(block_freq, 5, 4)) << ksl; + + // 4-bit sustain level, but 15 means 31 so effectively 5 bits + cache.eg_sustain = op_sustain_level(opoffs); + cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10; + cache.eg_sustain <<= 5; + + // The envelope diagram in the YM2413 datasheet gives values for these + // in ms from 0->48dB. The attack/decay tables give values in ms from + // 0->96dB, so to pick an equivalent decay rate, we want to find the + // closest match that is 2x the 0->48dB value: + // + // DP = 10ms (0->48db) -> 20ms (0->96db); decay of 12 gives 19.20ms + // RR = 310ms (0->48db) -> 620ms (0->96db); decay of 7 gives 613.76ms + // RS = 1200ms (0->48db) -> 2400ms (0->96db); decay of 5 gives 2455.04ms + // + // The envelope diagram for percussive sounds (eg_sustain() == 0) also uses + // "RR" to mean both the constant RR above and the Release Rate specified in + // the instrument data. In this case, Relief Pitcher's credit sound bears out + // that the Release Rate is used during sustain, and that the constant RR + // (or RS) is used during the release phase. + constexpr uint8_t DP = 12 * 4; + constexpr uint8_t RR = 7 * 4; + constexpr uint8_t RS = 5 * 4; + + // determine KSR adjustment for envelope rates + uint32_t ksrval = keycode >> (2 * (op_ksr(opoffs) ^ 1)); + cache.eg_rate[EG_DEPRESS] = DP; + cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 4, ksrval); + cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 4, ksrval); + if (op_eg_sustain(opoffs)) + { + cache.eg_rate[EG_SUSTAIN] = 0; + cache.eg_rate[EG_RELEASE] = ch_sustain(choffs) ? RS : effective_rate(op_release_rate(opoffs) * 4, ksrval); + } + else + { + cache.eg_rate[EG_SUSTAIN] = effective_rate(op_release_rate(opoffs) * 4, ksrval); + cache.eg_rate[EG_RELEASE] = ch_sustain(choffs) ? RS : RR; + } +} + + +//------------------------------------------------- +// compute_phase_step - compute the phase step +//------------------------------------------------- + +uint32_t opll_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm) +{ + // phase step computation is the same as OPL but the block_freq has one + // more bit, which we shift in + return opl_compute_phase_step(cache.block_freq << 1, cache.multiple, op_lfo_pm_enable(opoffs) ? lfo_raw_pm : 0); +} + + +//------------------------------------------------- +// log_keyon - log a key-on event +//------------------------------------------------- + +std::string opll_registers::log_keyon(uint32_t choffs, uint32_t opoffs) +{ + uint32_t chnum = choffs; + uint32_t opnum = opoffs; + + char buffer[256]; + char *end = &buffer[0]; + + end += sprintf(end, "%u.%02u freq=%04X inst=%X fb=%u mul=%X", + chnum, opnum, + ch_block_freq(choffs), + ch_instrument(choffs), + ch_feedback(choffs), + op_multiple(opoffs)); + + if (bitfield(opoffs, 0) == 1 || (is_rhythm(choffs) && choffs >= 6)) + end += sprintf(end, " vol=%X", op_volume(opoffs)); + else + end += sprintf(end, " tl=%02X", ch_total_level(choffs)); + + end += sprintf(end, " ksr=%u ksl=%u adr=%X/%X/%X sl=%X sus=%u/%u", + op_ksr(opoffs), + op_ksl(opoffs), + op_attack_rate(opoffs), + op_decay_rate(opoffs), + op_release_rate(opoffs), + op_sustain_level(opoffs), + op_eg_sustain(opoffs), + ch_sustain(choffs)); + + if (op_lfo_am_enable(opoffs)) + end += sprintf(end, " am=1"); + if (op_lfo_pm_enable(opoffs)) + end += sprintf(end, " pm=1"); + if (op_waveform(opoffs) != 0) + end += sprintf(end, " wf=1"); + if (is_rhythm(choffs)) + end += sprintf(end, " rhy=1"); + + return buffer; +} + + + +//********************************************************* +// YM3526 +//********************************************************* + +//------------------------------------------------- +// ym3526 - constructor +//------------------------------------------------- + +ym3526::ym3526(ymfm_interface &intf) : + m_address(0), + m_fm(intf) +{ +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ym3526::reset() +{ + // reset the engines + m_fm.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ym3526::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + m_fm.save_restore(state); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ym3526::read_status() +{ + return m_fm.status() | 0x06; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ym3526::read(uint32_t offset) +{ + uint8_t result = 0xff; + switch (offset & 1) + { + case 0: // status port + result = read_status(); + break; + + case 1: // when A0=1 datasheet says "the data on the bus are not guaranteed" + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ym3526::write_address(uint8_t data) +{ + // YM3526 doesn't expose a busy signal, and the datasheets don't indicate + // delays, but all other OPL chips need 12 cycles for address writes + m_fm.intf().ymfm_set_busy_end(12 * m_fm.clock_prescale()); + + // just set the address + m_address = data; +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym3526::write_data(uint8_t data) +{ + // YM3526 doesn't expose a busy signal, and the datasheets don't indicate + // delays, but all other OPL chips need 84 cycles for data writes + m_fm.intf().ymfm_set_busy_end(84 * m_fm.clock_prescale()); + + // write to FM + m_fm.write(m_address, data); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym3526::write(uint32_t offset, uint8_t data) +{ + switch (offset & 1) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate samples of sound +//------------------------------------------------- + +void ym3526::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // update the FM content; mixing details for YM3526 need verification + m_fm.output(output->clear(), 1, 32767, fm_engine::ALL_CHANNELS); + + // YM3526 uses an external DAC (YM3014) with mantissa/exponent format + // convert to 10.3 floating point value and back to simulate truncation + output->roundtrip_fp(); + } +} + + + +//********************************************************* +// Y8950 +//********************************************************* + +//------------------------------------------------- +// y8950 - constructor +//------------------------------------------------- + +y8950::y8950(ymfm_interface &intf) : + m_address(0), + m_io_ddr(0), + m_fm(intf), + m_adpcm_b(intf) +{ +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void y8950::reset() +{ + // reset the engines + m_fm.reset(); + m_adpcm_b.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void y8950::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + state.save_restore(m_io_ddr); + m_fm.save_restore(state); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t y8950::read_status() +{ + // start with current FM status, masking out bits we might set + uint8_t status = m_fm.status() & ~(STATUS_ADPCM_B_EOS | STATUS_ADPCM_B_BRDY | STATUS_ADPCM_B_PLAYING); + + // insert the live ADPCM status bits + uint8_t adpcm_status = m_adpcm_b.status(); + if ((adpcm_status & adpcm_b_channel::STATUS_EOS) != 0) + status |= STATUS_ADPCM_B_EOS; + if ((adpcm_status & adpcm_b_channel::STATUS_BRDY) != 0) + status |= STATUS_ADPCM_B_BRDY; + if ((adpcm_status & adpcm_b_channel::STATUS_PLAYING) != 0) + status |= STATUS_ADPCM_B_PLAYING; + + // run it through the FM engine to handle interrupts for us + return m_fm.set_reset_status(status, ~status); +} + + +//------------------------------------------------- +// read_data - read the data port +//------------------------------------------------- + +uint8_t y8950::read_data() +{ + uint8_t result = 0xff; + switch (m_address) + { + case 0x05: // keyboard in + result = m_fm.intf().ymfm_external_read(ACCESS_IO, 1); + break; + + case 0x09: // ADPCM data + case 0x1a: + result = m_adpcm_b.read(m_address - 0x07); + break; + + case 0x19: // I/O data + result = m_fm.intf().ymfm_external_read(ACCESS_IO, 0); + break; + + default: + debug::log_unexpected_read_write("Unexpected read from Y8950 data port %02X\n", m_address); + break; + } + return result; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t y8950::read(uint32_t offset) +{ + uint8_t result = 0xff; + switch (offset & 1) + { + case 0: // status port + result = read_status(); + break; + + case 1: // when A0=1 datasheet says "the data on the bus are not guaranteed" + result = read_data(); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void y8950::write_address(uint8_t data) +{ + // Y8950 doesn't expose a busy signal, but it does indicate that + // address writes should be no faster than every 12 clocks + m_fm.intf().ymfm_set_busy_end(12 * m_fm.clock_prescale()); + + // just set the address + m_address = data; +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void y8950::write_data(uint8_t data) +{ + // Y8950 doesn't expose a busy signal, but it does indicate that + // data writes should be no faster than every 12 clocks for + // registers 00-1A, or every 84 clocks for other registers + m_fm.intf().ymfm_set_busy_end(((m_address <= 0x1a) ? 12 : 84) * m_fm.clock_prescale()); + + // handle special addresses + switch (m_address) + { + case 0x04: // IRQ control + m_fm.write(m_address, data); + read_status(); + break; + + case 0x06: // keyboard out + m_fm.intf().ymfm_external_write(ACCESS_IO, 1, data); + break; + + case 0x08: // split FM/ADPCM-B + m_adpcm_b.write(m_address - 0x07, (data & 0x0f) | 0x80); + m_fm.write(m_address, data & 0xc0); + break; + + case 0x07: // ADPCM-B registers + case 0x09: + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + case 0x0e: + case 0x0f: + case 0x10: + case 0x11: + case 0x12: + case 0x15: + case 0x16: + case 0x17: + m_adpcm_b.write(m_address - 0x07, data); + break; + + case 0x18: // I/O direction + m_io_ddr = data & 0x0f; + break; + + case 0x19: // I/O data + m_fm.intf().ymfm_external_write(ACCESS_IO, 0, data & m_io_ddr); + break; + + default: // everything else to FM + m_fm.write(m_address, data); + break; + } +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void y8950::write(uint32_t offset, uint8_t data) +{ + switch (offset & 1) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate samples of sound +//------------------------------------------------- + +void y8950::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + m_adpcm_b.clock(); + + // update the FM content; clipping need verification + m_fm.output(output->clear(), 1, 32767, fm_engine::ALL_CHANNELS); + + // mix in the ADPCM; ADPCM-B is stereo, but only one channel + // not sure how it's wired up internally + m_adpcm_b.output(*output, 3); + + // Y8950 uses an external DAC (YM3014) with mantissa/exponent format + // convert to 10.3 floating point value and back to simulate truncation + output->roundtrip_fp(); + } +} + + + +//********************************************************* +// YM3812 +//********************************************************* + +//------------------------------------------------- +// ym3812 - constructor +//------------------------------------------------- + +ym3812::ym3812(ymfm_interface &intf) : + m_address(0), + m_fm(intf) +{ +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ym3812::reset() +{ + // reset the engines + m_fm.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ym3812::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + m_fm.save_restore(state); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ym3812::read_status() +{ + return m_fm.status() | 0x06; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ym3812::read(uint32_t offset) +{ + uint8_t result = 0xff; + switch (offset & 1) + { + case 0: // status port + result = read_status(); + break; + + case 1: // "inhibit" according to datasheet + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ym3812::write_address(uint8_t data) +{ + // YM3812 doesn't expose a busy signal, but it does indicate that + // address writes should be no faster than every 12 clocks + m_fm.intf().ymfm_set_busy_end(12 * m_fm.clock_prescale()); + + // just set the address + m_address = data; +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym3812::write_data(uint8_t data) +{ + // YM3812 doesn't expose a busy signal, but it does indicate that + // data writes should be no faster than every 84 clocks + m_fm.intf().ymfm_set_busy_end(84 * m_fm.clock_prescale()); + + // write to FM + m_fm.write(m_address, data); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym3812::write(uint32_t offset, uint8_t data) +{ + switch (offset & 1) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate samples of sound +//------------------------------------------------- + +void ym3812::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // update the FM content; mixing details for YM3812 need verification + m_fm.output(output->clear(), 1, 32767, fm_engine::ALL_CHANNELS); + + // YM3812 uses an external DAC (YM3014) with mantissa/exponent format + // convert to 10.3 floating point value and back to simulate truncation + output->roundtrip_fp(); + } +} + + + +//********************************************************* +// YMF262 +//********************************************************* + +//------------------------------------------------- +// ymf262 - constructor +//------------------------------------------------- + +ymf262::ymf262(ymfm_interface &intf) : + m_address(0), + m_fm(intf) +{ +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ymf262::reset() +{ + // reset the engines + m_fm.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ymf262::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + m_fm.save_restore(state); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ymf262::read_status() +{ + return m_fm.status(); +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ymf262::read(uint32_t offset) +{ + uint8_t result = 0xff; + switch (offset & 3) + { + case 0: // status port + result = read_status(); + break; + + case 1: + case 2: + case 3: + debug::log_unexpected_read_write("Unexpected read from YMF262 offset %d\n", offset & 3); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ymf262::write_address(uint8_t data) +{ + // YMF262 doesn't expose a busy signal, but it does indicate that + // address writes should be no faster than every 32 clocks + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); + + // just set the address + m_address = data; +} + + +//------------------------------------------------- +// write_data - handle a write to the data +// register +//------------------------------------------------- + +void ymf262::write_data(uint8_t data) +{ + // YMF262 doesn't expose a busy signal, but it does indicate that + // data writes should be no faster than every 32 clocks + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); + + // write to FM + m_fm.write(m_address, data); +} + + +//------------------------------------------------- +// write_address_hi - handle a write to the upper +// address register +//------------------------------------------------- + +void ymf262::write_address_hi(uint8_t data) +{ + // YMF262 doesn't expose a busy signal, but it does indicate that + // address writes should be no faster than every 32 clocks + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); + + // just set the address + m_address = data | 0x100; + + // tests reveal that in compatibility mode, upper bit is masked + // except for register 0x105 + if (m_fm.regs().newflag() == 0 && m_address != 0x105) + m_address &= 0xff; +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ymf262::write(uint32_t offset, uint8_t data) +{ + switch (offset & 3) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + + case 2: // address port + write_address_hi(data); + break; + + case 3: // data port + write_data(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate samples of sound +//------------------------------------------------- + +void ymf262::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // update the FM content; mixing details for YMF262 need verification + m_fm.output(output->clear(), 0, 32767, fm_engine::ALL_CHANNELS); + + // YMF262 output is 16-bit offset serial via YAC512 DAC + output->clamp16(); + } +} + + + +//********************************************************* +// YMF289B +//********************************************************* + +// YMF289B is a YMF262 with the following changes: +// * "Power down" mode added +// * Bulk register clear added +// * Busy flag added to the status register +// * Shorter busy times +// * All registers can be read +// * Only 2 outputs exposed + +//------------------------------------------------- +// ymf289b - constructor +//------------------------------------------------- + +ymf289b::ymf289b(ymfm_interface &intf) : + m_address(0), + m_fm(intf) +{ +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ymf289b::reset() +{ + // reset the engines + m_fm.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ymf289b::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + m_fm.save_restore(state); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ymf289b::read_status() +{ + uint8_t result = m_fm.status(); + + // YMF289B adds a busy flag + if (ymf289b_mode() && m_fm.intf().ymfm_is_busy()) + result |= STATUS_BUSY_FLAGS; + return result; +} + + +//------------------------------------------------- +// read_data - read the data register +//------------------------------------------------- + +uint8_t ymf289b::read_data() +{ + uint8_t result = 0xff; + + // YMF289B can read register data back + if (ymf289b_mode()) + result = m_fm.regs().read(m_address); + return result; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ymf289b::read(uint32_t offset) +{ + uint8_t result = 0xff; + switch (offset & 3) + { + case 0: // status port + result = read_status(); + break; + + case 1: // data port + result = read_data(); + break; + + case 2: + case 3: + debug::log_unexpected_read_write("Unexpected read from YMF289B offset %d\n", offset & 3); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ymf289b::write_address(uint8_t data) +{ + m_address = data; + + // count busy time + m_fm.intf().ymfm_set_busy_end(56); +} + + +//------------------------------------------------- +// write_data - handle a write to the data +// register +//------------------------------------------------- + +void ymf289b::write_data(uint8_t data) +{ + // write to FM + m_fm.write(m_address, data); + + // writes to 0x108 with the CLR flag set clear the registers + if (m_address == 0x108 && bitfield(data, 2) != 0) + m_fm.regs().reset(); + + // count busy time + m_fm.intf().ymfm_set_busy_end(56); +} + + +//------------------------------------------------- +// write_address_hi - handle a write to the upper +// address register +//------------------------------------------------- + +void ymf289b::write_address_hi(uint8_t data) +{ + // just set the address + m_address = data | 0x100; + + // tests reveal that in compatibility mode, upper bit is masked + // except for register 0x105 + if (m_fm.regs().newflag() == 0 && m_address != 0x105) + m_address &= 0xff; + + // count busy time + m_fm.intf().ymfm_set_busy_end(56); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ymf289b::write(uint32_t offset, uint8_t data) +{ + switch (offset & 3) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + + case 2: // address port + write_address_hi(data); + break; + + case 3: // data port + write_data(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate samples of sound +//------------------------------------------------- + +void ymf289b::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // update the FM content; mixing details for YMF262 need verification + fm_engine::output_data full; + m_fm.output(full.clear(), 0, 32767, fm_engine::ALL_CHANNELS); + + // YMF278B output is 16-bit offset serial via YAC512 DAC, but + // only 2 of the 4 outputs are exposed + output->data[0] = full.data[0]; + output->data[1] = full.data[1]; + output->clamp16(); + } +} + + + +//********************************************************* +// YMF278B +//********************************************************* + +//------------------------------------------------- +// ymf278b - constructor +//------------------------------------------------- + +ymf278b::ymf278b(ymfm_interface &intf) : + m_address(0), + m_fm_pos(0), + m_load_remaining(0), + m_next_status_id(false), + m_fm(intf), + m_pcm(intf) +{ +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ymf278b::reset() +{ + // reset the engines + m_fm.reset(); + m_pcm.reset(); + + // next status read will return ID + m_next_status_id = true; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ymf278b::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + state.save_restore(m_fm_pos); + state.save_restore(m_load_remaining); + state.save_restore(m_next_status_id); + m_fm.save_restore(state); + m_pcm.save_restore(state); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ymf278b::read_status() +{ + uint8_t result; + + // first status read after initialization returns a chip ID, which + // varies based on the "new" flags, indicating the mode + if (m_next_status_id) + { + if (m_fm.regs().new2flag()) + result = 0x02; + else if (m_fm.regs().newflag()) + result = 0x00; + else + result = 0x06; + m_next_status_id = false; + } + else + { + result = m_fm.status(); + if (m_fm.intf().ymfm_is_busy()) + result |= STATUS_BUSY; + if (m_load_remaining != 0) + result |= STATUS_LD; + + // if new2 flag is not set, we're in OPL2 or OPL3 mode + if (!m_fm.regs().new2flag()) + result &= ~(STATUS_BUSY | STATUS_LD); + } + return result; +} + + +//------------------------------------------------- +// write_data_pcm - handle a write to the PCM data +// register +//------------------------------------------------- + +uint8_t ymf278b::read_data_pcm() +{ + // write to FM + if (bitfield(m_address, 9) != 0) + return m_pcm.read(m_address & 0xff); + return 0; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ymf278b::read(uint32_t offset) +{ + uint8_t result = 0xff; + switch (offset & 7) + { + case 0: // status port + result = read_status(); + break; + + case 5: // PCM data port + result = read_data_pcm(); + break; + + default: + debug::log_unexpected_read_write("Unexpected read from ymf278b offset %d\n", offset & 3); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ymf278b::write_address(uint8_t data) +{ + // just set the address + m_address = data; +} + + +//------------------------------------------------- +// write_data - handle a write to the data +// register +//------------------------------------------------- + +void ymf278b::write_data(uint8_t data) +{ + // write to FM + if (bitfield(m_address, 9) == 0) + { + uint8_t old = m_fm.regs().new2flag(); + m_fm.write(m_address, data); + + // changing NEW2 from 0->1 causes the next status read to + // return the chip ID + if (old == 0 && m_fm.regs().new2flag() != 0) + m_next_status_id = true; + } + + // BUSY goes for 56 clocks on FM writes + m_fm.intf().ymfm_set_busy_end(56); +} + + +//------------------------------------------------- +// write_address_hi - handle a write to the upper +// address register +//------------------------------------------------- + +void ymf278b::write_address_hi(uint8_t data) +{ + // just set the address + m_address = data | 0x100; + + // YMF262, in compatibility mode, treats the upper bit as masked + // except for register 0x105; assuming YMF278B works the same way? + if (m_fm.regs().newflag() == 0 && m_address != 0x105) + m_address &= 0xff; +} + + +//------------------------------------------------- +// write_address_pcm - handle a write to the upper +// address register +//------------------------------------------------- + +void ymf278b::write_address_pcm(uint8_t data) +{ + // just set the address + m_address = data | 0x200; +} + + +//------------------------------------------------- +// write_data_pcm - handle a write to the PCM data +// register +//------------------------------------------------- + +void ymf278b::write_data_pcm(uint8_t data) +{ + // ignore data writes if new2 is not yet set + if (m_fm.regs().new2flag() == 0) + return; + + // write to FM + if (bitfield(m_address, 9) != 0) + { + uint8_t addr = m_address & 0xff; + m_pcm.write(addr, data); + + // writes to the waveform number cause loads to happen for "about 300usec" + // which is ~13 samples at the nominal output frequency of 44.1kHz + if (addr >= 0x08 && addr <= 0x1f) + m_load_remaining = 13; + } + + // BUSY goes for 88 clocks on PCM writes + m_fm.intf().ymfm_set_busy_end(88); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ymf278b::write(uint32_t offset, uint8_t data) +{ + switch (offset & 7) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + + case 2: // address port + write_address_hi(data); + break; + + case 3: // data port + write_data(data); + break; + + case 4: // PCM address port + write_address_pcm(data); + break; + + case 5: // PCM address port + write_data_pcm(data); + break; + + default: + debug::log_unexpected_read_write("Unexpected write to ymf278b offset %d\n", offset & 7); + break; + } +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void ymf278b::generate(output_data *output, uint32_t numsamples) +{ + static const int16_t s_mix_scale[8] = { 0x7fa, 0x5a4, 0x3fd, 0x2d2, 0x1fe, 0x169, 0xff, 0 }; + int32_t const pcm_l = s_mix_scale[m_pcm.regs().mix_pcm_l()]; + int32_t const pcm_r = s_mix_scale[m_pcm.regs().mix_pcm_r()]; + int32_t const fm_l = s_mix_scale[m_pcm.regs().mix_fm_l()]; + int32_t const fm_r = s_mix_scale[m_pcm.regs().mix_fm_r()]; + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm_pos += FM_EXTRA_SAMPLE_STEP; + if (m_fm_pos >= FM_EXTRA_SAMPLE_THRESH) + { + m_fm.clock(fm_engine::ALL_CHANNELS); + m_fm_pos -= FM_EXTRA_SAMPLE_THRESH; + } + m_fm.clock(fm_engine::ALL_CHANNELS); + m_pcm.clock(pcm_engine::ALL_CHANNELS); + + // update the FM content; mixing details for YMF278B need verification + fm_engine::output_data fmout; + m_fm.output(fmout.clear(), 0, 32767, fm_engine::ALL_CHANNELS); + + // update the PCM content + pcm_engine::output_data pcmout; + m_pcm.output(pcmout.clear(), pcm_engine::ALL_CHANNELS); + + // DO0 output: FM channels 2+3 only + output->data[0] = fmout.data[2]; + output->data[1] = fmout.data[3]; + + // DO1 output: wavetable channels 2+3 only + output->data[2] = pcmout.data[2]; + output->data[3] = pcmout.data[3]; + + // DO2 output: mixed FM channels 0+1 and wavetable channels 0+1 + output->data[4] = (fmout.data[0] * fm_l + pcmout.data[0] * pcm_l) >> 11; + output->data[5] = (fmout.data[1] * fm_r + pcmout.data[1] * pcm_r) >> 11; + + // YMF278B output is 16-bit 2s complement serial + output->clamp16(); + } + + // decrement the load waiting count + if (m_load_remaining > 0) + m_load_remaining -= std::min(m_load_remaining, numsamples); +} + + + +//********************************************************* +// OPLL BASE +//********************************************************* + +//------------------------------------------------- +// opll_base - constructor +//------------------------------------------------- + +opll_base::opll_base(ymfm_interface &intf, uint8_t const *instrument_data) : + m_address(0), + m_fm(intf) +{ + m_fm.regs().set_instrument_data(instrument_data); +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void opll_base::reset() +{ + // reset the engines + m_fm.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void opll_base::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + m_fm.save_restore(state); +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void opll_base::write_address(uint8_t data) +{ + // OPLL doesn't expose a busy signal, but datasheets are pretty consistent + // in indicating that address writes should be no faster than every 12 clocks + m_fm.intf().ymfm_set_busy_end(12); + + // just set the address + m_address = data; +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void opll_base::write_data(uint8_t data) +{ + // OPLL doesn't expose a busy signal, but datasheets are pretty consistent + // in indicating that address writes should be no faster than every 84 clocks + m_fm.intf().ymfm_set_busy_end(84); + + // write to FM + m_fm.write(m_address, data); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void opll_base::write(uint32_t offset, uint8_t data) +{ + switch (offset & 1) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void opll_base::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // update the FM content; OPLL has a built-in 9-bit DAC + m_fm.output(output->clear(), 5, 256, fm_engine::ALL_CHANNELS); + + // final output is multiplexed; we don't simulate that here except + // to average over everything + output->data[0] = (output->data[0] * 128) / 9; + output->data[1] = (output->data[1] * 128) / 9; + } +} + + + +//********************************************************* +// YM2413 +//********************************************************* + +//------------------------------------------------- +// ym2413 - constructor +//------------------------------------------------- + +ym2413::ym2413(ymfm_interface &intf, uint8_t const *instrument_data) : + opll_base(intf, (instrument_data != nullptr) ? instrument_data : s_default_instruments) +{ +}; + +// table below taken from https://github.com/plgDavid/misc/wiki/Copyright-free-OPLL(x)-ROM-patches +uint8_t const ym2413::s_default_instruments[] = +{ + //April 2015 David Viens, tweaked May 19-21th 2015 Hubert Lamontagne + 0x71, 0x61, 0x1E, 0x17, 0xEF, 0x7F, 0x00, 0x17, //Violin + 0x13, 0x41, 0x1A, 0x0D, 0xF8, 0xF7, 0x23, 0x13, //Guitar + 0x13, 0x01, 0x99, 0x00, 0xF2, 0xC4, 0x11, 0x23, //Piano + 0x31, 0x61, 0x0E, 0x07, 0x98, 0x64, 0x70, 0x27, //Flute + 0x22, 0x21, 0x1E, 0x06, 0xBF, 0x76, 0x00, 0x28, //Clarinet + 0x31, 0x22, 0x16, 0x05, 0xE0, 0x71, 0x0F, 0x18, //Oboe + 0x21, 0x61, 0x1D, 0x07, 0x82, 0x8F, 0x10, 0x07, //Trumpet + 0x23, 0x21, 0x2D, 0x14, 0xFF, 0x7F, 0x00, 0x07, //Organ + 0x41, 0x61, 0x1B, 0x06, 0x64, 0x65, 0x10, 0x17, //Horn + 0x61, 0x61, 0x0B, 0x18, 0x85, 0xFF, 0x81, 0x07, //Synthesizer + 0x13, 0x01, 0x83, 0x11, 0xFA, 0xE4, 0x10, 0x04, //Harpsichord + 0x17, 0x81, 0x23, 0x07, 0xF8, 0xF8, 0x22, 0x12, //Vibraphone + 0x61, 0x50, 0x0C, 0x05, 0xF2, 0xF5, 0x29, 0x42, //Synthesizer Bass + 0x01, 0x01, 0x54, 0x03, 0xC3, 0x92, 0x03, 0x02, //Acoustic Bass + 0x41, 0x41, 0x89, 0x03, 0xF1, 0xE5, 0x11, 0x13, //Electric Guitar + 0x01, 0x01, 0x18, 0x0F, 0xDF, 0xF8, 0x6A, 0x6D, //rhythm 1 + 0x01, 0x01, 0x00, 0x00, 0xC8, 0xD8, 0xA7, 0x48, //rhythm 2 + 0x05, 0x01, 0x00, 0x00, 0xF8, 0xAA, 0x59, 0x55 //rhythm 3 +}; + + + +//********************************************************* +// YM2423 +//********************************************************* + +//------------------------------------------------- +// ym2423 - constructor +//------------------------------------------------- + +ym2423::ym2423(ymfm_interface &intf, uint8_t const *instrument_data) : + opll_base(intf, (instrument_data != nullptr) ? instrument_data : s_default_instruments) +{ +}; + +// table below taken from https://github.com/plgDavid/misc/wiki/Copyright-free-OPLL(x)-ROM-patches +uint8_t const ym2423::s_default_instruments[] = +{ + // May 4-6 2016 Hubert Lamontagne + // Doesn't seem to have any diff between opllx-x and opllx-y + // Drums seem identical to regular opll + 0x61, 0x61, 0x1B, 0x07, 0x94, 0x5F, 0x10, 0x06, //1 Strings Saw wave with vibrato Violin + 0x93, 0xB1, 0x51, 0x04, 0xF3, 0xF2, 0x70, 0xFB, //2 Guitar Jazz GuitarPiano + 0x41, 0x21, 0x11, 0x85, 0xF2, 0xF2, 0x70, 0x75, //3 Electric Guitar Same as OPLL No.15 Synth + 0x93, 0xB2, 0x28, 0x07, 0xF3, 0xF2, 0x70, 0xB4, //4 Electric Piano 2 Slow attack, tremoloDing-a-ling + 0x72, 0x31, 0x97, 0x05, 0x51, 0x6F, 0x60, 0x09, //5 Flute Same as OPLL No.4Clarinet + 0x13, 0x30, 0x18, 0x06, 0xF7, 0xF4, 0x50, 0x85, //6 Marimba Also be used as steel drumXyophone + 0x51, 0x31, 0x1C, 0x07, 0x51, 0x71, 0x20, 0x26, //7 Trumpet Same as OPLL No.7Trumpet + 0x41, 0xF4, 0x1B, 0x07, 0x74, 0x34, 0x00, 0x06, //8 Harmonica Harmonica synth + 0x50, 0x30, 0x4D, 0x03, 0x42, 0x65, 0x20, 0x06, //9 Tuba Tuba + 0x40, 0x20, 0x10, 0x85, 0xF3, 0xF5, 0x20, 0x04, //10 Synth Brass 2 Synth sweep + 0x61, 0x61, 0x1B, 0x07, 0xC5, 0x96, 0xF3, 0xF6, //11 Short Saw Saw wave with short envelopeSynth hit + 0xF9, 0xF1, 0xDC, 0x00, 0xF5, 0xF3, 0x77, 0xF2, //12 Vibraphone Bright vibraphoneVibes + 0x60, 0xA2, 0x91, 0x03, 0x94, 0xC1, 0xF7, 0xF7, //13 Electric Guitar 2 Clean guitar with feedbackHarmonic bass + 0x30, 0x30, 0x17, 0x06, 0xF3, 0xF1, 0xB7, 0xFC, //14 Synth Bass 2Snappy bass + 0x31, 0x36, 0x0D, 0x05, 0xF2, 0xF4, 0x27, 0x9C, //15 Sitar Also be used as ShamisenBanjo + 0x01, 0x01, 0x18, 0x0F, 0xDF, 0xF8, 0x6A, 0x6D, //rhythm 1 + 0x01, 0x01, 0x00, 0x00, 0xC8, 0xD8, 0xA7, 0x48, //rhythm 2 + 0x05, 0x01, 0x00, 0x00, 0xF8, 0xAA, 0x59, 0x55 //rhythm 3 +}; + + + +//********************************************************* +// YMF281 +//********************************************************* + +//------------------------------------------------- +// ymf281 - constructor +//------------------------------------------------- + +ymf281::ymf281(ymfm_interface &intf, uint8_t const *instrument_data) : + opll_base(intf, (instrument_data != nullptr) ? instrument_data : s_default_instruments) +{ +}; + +// table below taken from https://github.com/plgDavid/misc/wiki/Copyright-free-OPLL(x)-ROM-patches +uint8_t const ymf281::s_default_instruments[] = +{ + // May 14th 2015 Hubert Lamontagne + 0x72, 0x21, 0x1A, 0x07, 0xF6, 0x64, 0x01, 0x16, // Clarinet ~~ Electric String Square wave with vibrato + 0x00, 0x10, 0x45, 0x00, 0xF6, 0x83, 0x73, 0x63, // Synth Bass ~~ Bow wow Triangular wave + 0x13, 0x01, 0x96, 0x00, 0xF1, 0xF4, 0x31, 0x23, // Piano ~~ Electric Guitar Despite of its name, same as Piano of YM2413. + 0x71, 0x21, 0x0B, 0x0F, 0xF9, 0x64, 0x70, 0x17, // Flute ~~ Organ Sine wave + 0x02, 0x21, 0x1E, 0x06, 0xF9, 0x76, 0x00, 0x28, // Square Wave ~~ Clarinet Same as ones of YM2413. + 0x00, 0x61, 0x82, 0x0E, 0xF9, 0x61, 0x20, 0x27, // Space Oboe ~~ Saxophone Saw wave with vibrato + 0x21, 0x61, 0x1B, 0x07, 0x84, 0x8F, 0x10, 0x07, // Trumpet ~~ Trumpet Same as ones of YM2413. + 0x37, 0x32, 0xCA, 0x02, 0x66, 0x64, 0x47, 0x29, // Wow Bell ~~ Street Organ Calliope + 0x41, 0x41, 0x07, 0x03, 0xF5, 0x70, 0x51, 0xF5, // Electric Guitar ~~ Synth Brass Same as Synthesizer of YM2413. + 0x36, 0x01, 0x5E, 0x07, 0xF2, 0xF3, 0xF7, 0xF7, // Vibes ~~ Electric Piano Simulate of Rhodes Piano + 0x00, 0x00, 0x18, 0x06, 0xC5, 0xF3, 0x20, 0xF2, // Bass ~~ Bass Electric bass + 0x17, 0x81, 0x25, 0x07, 0xF7, 0xF3, 0x21, 0xF7, // Vibraphone ~~ Vibraphone Same as ones of YM2413. + 0x35, 0x64, 0x00, 0x00, 0xFF, 0xF3, 0x77, 0xF5, // Vibrato Bell ~~ Chime Bell + 0x11, 0x31, 0x00, 0x07, 0xDD, 0xF3, 0xFF, 0xFB, // Click Sine ~~ Tom Tom II Tom + 0x3A, 0x21, 0x00, 0x07, 0x95, 0x84, 0x0F, 0xF5, // Noise and Tone ~~ Noise for S.E. + 0x01, 0x01, 0x18, 0x0F, 0xDF, 0xF8, 0x6A, 0x6D, //rhythm 1 + 0x01, 0x01, 0x00, 0x00, 0xC8, 0xD8, 0xA7, 0x48, //rhythm 2 + 0x05, 0x01, 0x00, 0x00, 0xF8, 0xAA, 0x59, 0x55 //rhythm 3 +}; + + + +//********************************************************* +// DS1001 +//********************************************************* + +//------------------------------------------------- +// ds1001 - constructor +//------------------------------------------------- + +ds1001::ds1001(ymfm_interface &intf, uint8_t const *instrument_data) : + opll_base(intf, (instrument_data != nullptr) ? instrument_data : s_default_instruments) +{ +}; + +// table below taken from https://github.com/plgDavid/misc/wiki/Copyright-free-OPLL(x)-ROM-patches +uint8_t const ds1001::s_default_instruments[] = +{ + // May 15th 2015 Hubert Lamontagne & David Viens + 0x03, 0x21, 0x05, 0x06, 0xC8, 0x81, 0x42, 0x27, // Buzzy Bell + 0x13, 0x41, 0x14, 0x0D, 0xF8, 0xF7, 0x23, 0x12, // Guitar + 0x31, 0x11, 0x08, 0x08, 0xFA, 0xC2, 0x28, 0x22, // Wurly + 0x31, 0x61, 0x0C, 0x07, 0xF8, 0x64, 0x60, 0x27, // Flute + 0x22, 0x21, 0x1E, 0x06, 0xFF, 0x76, 0x00, 0x28, // Clarinet + 0x02, 0x01, 0x05, 0x00, 0xAC, 0xF2, 0x03, 0x02, // Synth + 0x21, 0x61, 0x1D, 0x07, 0x82, 0x8F, 0x10, 0x07, // Trumpet + 0x23, 0x21, 0x22, 0x17, 0xFF, 0x73, 0x00, 0x17, // Organ + 0x15, 0x11, 0x25, 0x00, 0x41, 0x71, 0x00, 0xF1, // Bells + 0x95, 0x01, 0x10, 0x0F, 0xB8, 0xAA, 0x50, 0x02, // Vibes + 0x17, 0xC1, 0x5E, 0x07, 0xFA, 0xF8, 0x22, 0x12, // Vibraphone + 0x71, 0x23, 0x11, 0x06, 0x65, 0x74, 0x10, 0x16, // Tutti + 0x01, 0x02, 0xD3, 0x05, 0xF3, 0x92, 0x83, 0xF2, // Fretless + 0x61, 0x63, 0x0C, 0x00, 0xA4, 0xFF, 0x30, 0x06, // Synth Bass + 0x21, 0x62, 0x0D, 0x00, 0xA1, 0xFF, 0x50, 0x08, // Sweep + 0x01, 0x01, 0x18, 0x0F, 0xDF, 0xF8, 0x6A, 0x6D, //rhythm 1 + 0x01, 0x01, 0x00, 0x00, 0xC8, 0xD8, 0xA7, 0x48, //rhythm 2 + 0x05, 0x01, 0x00, 0x00, 0xF8, 0xAA, 0x59, 0x55 //rhythm 3 +}; + + +//********************************************************* +// EXPLICIT INSTANTIATION +//********************************************************* + +template class opl_registers_base<4>; +template class fm_engine_base>; + +} diff --git a/src/sound/ymfm/ymfm_opl.h b/src/sound/ymfm/ymfm_opl.h new file mode 100644 index 000000000..843e5b274 --- /dev/null +++ b/src/sound/ymfm/ymfm_opl.h @@ -0,0 +1,902 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_OPL_H +#define YMFM_OPL_H + +#pragma once + +#include "ymfm.h" +#include "ymfm_adpcm.h" +#include "ymfm_fm.h" +#include "ymfm_pcm.h" + +namespace ymfm +{ + +//********************************************************* +// REGISTER CLASSES +//********************************************************* + +// ======================> opl_registers_base + +// +// OPL/OPL2/OPL3/OPL4 register map: +// +// System-wide registers: +// 01 xxxxxxxx Test register +// --x----- Enable OPL compatibility mode [OPL2 only] (1 = enable) +// 02 xxxxxxxx Timer A value (4 * OPN) +// 03 xxxxxxxx Timer B value +// 04 x------- RST +// -x------ Mask timer A +// --x----- Mask timer B +// ------x- Load timer B +// -------x Load timer A +// 08 x------- CSM mode [OPL/OPL2 only] +// -x------ Note select +// BD x------- AM depth +// -x------ PM depth +// --x----- Rhythm enable +// ---x---- Bass drum key on +// ----x--- Snare drum key on +// -----x-- Tom key on +// ------x- Top cymbal key on +// -------x High hat key on +// 101 --xxxxxx Test register 2 [OPL3 only] +// 104 --x----- Channel 6 4-operator mode [OPL3 only] +// ---x---- Channel 5 4-operator mode [OPL3 only] +// ----x--- Channel 4 4-operator mode [OPL3 only] +// -----x-- Channel 3 4-operator mode [OPL3 only] +// ------x- Channel 2 4-operator mode [OPL3 only] +// -------x Channel 1 4-operator mode [OPL3 only] +// 105 -------x New [OPL3 only] +// ------x- New2 [OPL4 only] +// +// Per-channel registers (channel in address bits 0-3) +// Note that all these apply to address+100 as well on OPL3+ +// A0-A8 xxxxxxxx F-number (low 8 bits) +// B0-B8 --x----- Key on +// ---xxx-- Block (octvate, 0-7) +// ------xx F-number (high two bits) +// C0-C8 x------- CHD output (to DO0 pin) [OPL3+ only] +// -x------ CHC output (to DO0 pin) [OPL3+ only] +// --x----- CHB output (mixed right, to DO2 pin) [OPL3+ only] +// ---x---- CHA output (mixed left, to DO2 pin) [OPL3+ only] +// ----xxx- Feedback level for operator 1 (0-7) +// -------x Operator connection algorithm +// +// Per-operator registers (operator in bits 0-5) +// Note that all these apply to address+100 as well on OPL3+ +// 20-35 x------- AM enable +// -x------ PM enable (VIB) +// --x----- EG type +// ---x---- Key scale rate +// ----xxxx Multiple value (0-15) +// 40-55 xx------ Key scale level (0-3) +// --xxxxxx Total level (0-63) +// 60-75 xxxx---- Attack rate (0-15) +// ----xxxx Decay rate (0-15) +// 80-95 xxxx---- Sustain level (0-15) +// ----xxxx Release rate (0-15) +// E0-F5 ------xx Wave select (0-3) [OPL2 only] +// -----xxx Wave select (0-7) [OPL3+ only] +// + +template +class opl_registers_base : public fm_registers_base +{ + static constexpr bool IsOpl2 = (Revision == 2); + static constexpr bool IsOpl2Plus = (Revision >= 2); + static constexpr bool IsOpl3Plus = (Revision >= 3); + static constexpr bool IsOpl4Plus = (Revision >= 4); + +public: + // constants + static constexpr uint32_t OUTPUTS = IsOpl3Plus ? 4 : 1; + static constexpr uint32_t CHANNELS = IsOpl3Plus ? 18 : 9; + static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1; + static constexpr uint32_t OPERATORS = CHANNELS * 2; + static constexpr uint32_t WAVEFORMS = IsOpl3Plus ? 8 : (IsOpl2Plus ? 4 : 1); + static constexpr uint32_t REGISTERS = IsOpl3Plus ? 0x200 : 0x100; + static constexpr uint32_t REG_MODE = 0x04; + static constexpr uint32_t DEFAULT_PRESCALE = IsOpl4Plus ? 19 : (IsOpl3Plus ? 8 : 4); + static constexpr uint32_t EG_CLOCK_DIVIDER = 1; + static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS; + static constexpr bool DYNAMIC_OPS = IsOpl3Plus; + static constexpr bool MODULATOR_DELAY = !IsOpl3Plus; + static constexpr uint8_t STATUS_TIMERA = 0x40; + static constexpr uint8_t STATUS_TIMERB = 0x20; + static constexpr uint8_t STATUS_BUSY = 0; + static constexpr uint8_t STATUS_IRQ = 0x80; + + // constructor + opl_registers_base(); + + // reset to initial state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // map channel number to register offset + static constexpr uint32_t channel_offset(uint32_t chnum) + { + assert(chnum < CHANNELS); + if (!IsOpl3Plus) + return chnum; + else + return (chnum % 9) + 0x100 * (chnum / 9); + } + + // map operator number to register offset + static constexpr uint32_t operator_offset(uint32_t opnum) + { + assert(opnum < OPERATORS); + if (!IsOpl3Plus) + return opnum + 2 * (opnum / 6); + else + return (opnum % 18) + 2 * ((opnum % 18) / 6) + 0x100 * (opnum / 18); + } + + // return an array of operator indices for each channel + struct operator_mapping { uint32_t chan[CHANNELS]; }; + void operator_map(operator_mapping &dest) const; + + // OPL4 apparently can read back FM registers? + uint8_t read(uint16_t index) const { return m_regdata[index]; } + + // handle writes to the register array + bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask); + + // clock the noise and LFO, if present, returning LFO PM value + int32_t clock_noise_and_lfo(); + + // reset the LFO + void reset_lfo() { m_lfo_am_counter = m_lfo_pm_counter = 0; } + + // return the AM offset from LFO for the given channel + // on OPL this is just a fixed value + uint32_t lfo_am_offset(uint32_t choffs) const { return m_lfo_am; } + + // return LFO/noise states + uint32_t noise_state() const { return m_noise_lfsr >> 23; } + + // caching helpers + void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache); + + // compute the phase step, given a PM value + uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm); + + // log a key-on event + std::string log_keyon(uint32_t choffs, uint32_t opoffs); + + // system-wide registers + uint32_t test() const { return byte(0x01, 0, 8); } + uint32_t waveform_enable() const { return IsOpl2 ? byte(0x01, 5, 1) : (IsOpl3Plus ? 1 : 0); } + uint32_t timer_a_value() const { return byte(0x02, 0, 8) * 4; } // 8->10 bits + uint32_t timer_b_value() const { return byte(0x03, 0, 8); } + uint32_t status_mask() const { return byte(0x04, 0, 8) & 0x78; } + uint32_t irq_reset() const { return byte(0x04, 7, 1); } + uint32_t reset_timer_b() const { return byte(0x04, 7, 1) | byte(0x04, 5, 1); } + uint32_t reset_timer_a() const { return byte(0x04, 7, 1) | byte(0x04, 6, 1); } + uint32_t enable_timer_b() const { return 1; } + uint32_t enable_timer_a() const { return 1; } + uint32_t load_timer_b() const { return byte(0x04, 1, 1); } + uint32_t load_timer_a() const { return byte(0x04, 0, 1); } + uint32_t csm() const { return IsOpl3Plus ? 0 : byte(0x08, 7, 1); } + uint32_t note_select() const { return byte(0x08, 6, 1); } + uint32_t lfo_am_depth() const { return byte(0xbd, 7, 1); } + uint32_t lfo_pm_depth() const { return byte(0xbd, 6, 1); } + uint32_t rhythm_enable() const { return byte(0xbd, 5, 1); } + uint32_t rhythm_keyon() const { return byte(0xbd, 4, 0); } + uint32_t newflag() const { return IsOpl3Plus ? byte(0x105, 0, 1) : 0; } + uint32_t new2flag() const { return IsOpl4Plus ? byte(0x105, 1, 1) : 0; } + uint32_t fourop_enable() const { return IsOpl3Plus ? byte(0x104, 0, 6) : 0; } + + // per-channel registers + uint32_t ch_block_freq(uint32_t choffs) const { return word(0xb0, 0, 5, 0xa0, 0, 8, choffs); } + uint32_t ch_feedback(uint32_t choffs) const { return byte(0xc0, 1, 3, choffs); } + uint32_t ch_algorithm(uint32_t choffs) const { return byte(0xc0, 0, 1, choffs) | (IsOpl3Plus ? (8 | (byte(0xc3, 0, 1, choffs) << 1)) : 0); } + uint32_t ch_output_any(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 4, 4) : 1; } + uint32_t ch_output_0(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 4, 1) : 1; } + uint32_t ch_output_1(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 5, 1) : (IsOpl3Plus ? 1 : 0); } + uint32_t ch_output_2(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 6, 1) : 0; } + uint32_t ch_output_3(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 7, 1) : 0; } + + // per-operator registers + uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0x20, 7, 1, opoffs); } + uint32_t op_lfo_pm_enable(uint32_t opoffs) const { return byte(0x20, 6, 1, opoffs); } + uint32_t op_eg_sustain(uint32_t opoffs) const { return byte(0x20, 5, 1, opoffs); } + uint32_t op_ksr(uint32_t opoffs) const { return byte(0x20, 4, 1, opoffs); } + uint32_t op_multiple(uint32_t opoffs) const { return byte(0x20, 0, 4, opoffs); } + uint32_t op_ksl(uint32_t opoffs) const { uint32_t temp = byte(0x40, 6, 2, opoffs); return bitfield(temp, 1) | (bitfield(temp, 0) << 1); } + uint32_t op_total_level(uint32_t opoffs) const { return byte(0x40, 0, 6, opoffs); } + uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x60, 4, 4, opoffs); } + uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0x60, 0, 4, opoffs); } + uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0x80, 4, 4, opoffs); } + uint32_t op_release_rate(uint32_t opoffs) const { return byte(0x80, 0, 4, opoffs); } + uint32_t op_waveform(uint32_t opoffs) const { return IsOpl2Plus ? byte(0xe0, 0, newflag() ? 3 : 2, opoffs) : 0; } + +protected: + // return a bitfield extracted from a byte + uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const + { + return bitfield(m_regdata[offset + extra_offset], start, count); + } + + // return a bitfield extracted from a pair of bytes, MSBs listed first + uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const + { + return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset); + } + + // helper to determine if the this channel is an active rhythm channel + bool is_rhythm(uint32_t choffs) const + { + return rhythm_enable() && (choffs >= 6 && choffs <= 8); + } + + // internal state + uint16_t m_lfo_am_counter; // LFO AM counter + uint16_t m_lfo_pm_counter; // LFO PM counter + uint32_t m_noise_lfsr; // noise LFSR state + uint8_t m_lfo_am; // current LFO AM value + uint8_t m_regdata[REGISTERS]; // register data + uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms +}; + +using opl_registers = opl_registers_base<1>; +using opl2_registers = opl_registers_base<2>; +using opl3_registers = opl_registers_base<3>; +using opl4_registers = opl_registers_base<4>; + + + +// ======================> opll_registers + +// +// OPLL register map: +// +// System-wide registers: +// 0E --x----- Rhythm enable +// ---x---- Bass drum key on +// ----x--- Snare drum key on +// -----x-- Tom key on +// ------x- Top cymbal key on +// -------x High hat key on +// 0F xxxxxxxx Test register +// +// Per-channel registers (channel in address bits 0-3) +// 10-18 xxxxxxxx F-number (low 8 bits) +// 20-28 --x----- Sustain on +// ---x---- Key on +// --- xxx- Block (octvate, 0-7) +// -------x F-number (high bit) +// 30-38 xxxx---- Instrument selection +// ----xxxx Volume +// +// User instrument registers (for carrier, modulator operators) +// 00-01 x------- AM enable +// -x------ PM enable (VIB) +// --x----- EG type +// ---x---- Key scale rate +// ----xxxx Multiple value (0-15) +// 02 xx------ Key scale level (carrier, 0-3) +// --xxxxxx Total level (modulator, 0-63) +// 03 xx------ Key scale level (modulator, 0-3) +// ---x---- Rectified wave (carrier) +// ----x--- Rectified wave (modulator) +// -----xxx Feedback level for operator 1 (0-7) +// 04-05 xxxx---- Attack rate (0-15) +// ----xxxx Decay rate (0-15) +// 06-07 xxxx---- Sustain level (0-15) +// ----xxxx Release rate (0-15) +// +// Internal (fake) registers: +// 40-48 xxxxxxxx Current instrument base address +// 4E-5F xxxxxxxx Current instrument base address + operator slot (0/1) +// 70-FF xxxxxxxx Data for instruments (1-16 plus 3 drums) +// + +class opll_registers : public fm_registers_base +{ +public: + static constexpr uint32_t OUTPUTS = 2; + static constexpr uint32_t CHANNELS = 9; + static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1; + static constexpr uint32_t OPERATORS = CHANNELS * 2; + static constexpr uint32_t WAVEFORMS = 2; + static constexpr uint32_t REGISTERS = 0x40; + static constexpr uint32_t REG_MODE = 0x3f; + static constexpr uint32_t DEFAULT_PRESCALE = 4; + static constexpr uint32_t EG_CLOCK_DIVIDER = 1; + static constexpr uint32_t CSM_TRIGGER_MASK = 0; + static constexpr bool EG_HAS_DEPRESS = true; + static constexpr bool MODULATOR_DELAY = true; + static constexpr uint8_t STATUS_TIMERA = 0; + static constexpr uint8_t STATUS_TIMERB = 0; + static constexpr uint8_t STATUS_BUSY = 0; + static constexpr uint8_t STATUS_IRQ = 0; + + // OPLL-specific constants + static constexpr uint32_t INSTDATA_SIZE = 0x90; + + // constructor + opll_registers(); + + // reset to initial state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // map channel number to register offset + static constexpr uint32_t channel_offset(uint32_t chnum) + { + assert(chnum < CHANNELS); + return chnum; + } + + // map operator number to register offset + static constexpr uint32_t operator_offset(uint32_t opnum) + { + assert(opnum < OPERATORS); + return opnum; + } + + // return an array of operator indices for each channel + struct operator_mapping { uint32_t chan[CHANNELS]; }; + void operator_map(operator_mapping &dest) const; + + // read a register value + uint8_t read(uint16_t index) const { return m_regdata[index]; } + + // handle writes to the register array + bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask); + + // clock the noise and LFO, if present, returning LFO PM value + int32_t clock_noise_and_lfo(); + + // reset the LFO + void reset_lfo() { m_lfo_am_counter = m_lfo_pm_counter = 0; } + + // return the AM offset from LFO for the given channel + // on OPL this is just a fixed value + uint32_t lfo_am_offset(uint32_t choffs) const { return m_lfo_am; } + + // return LFO/noise states + uint32_t noise_state() const { return m_noise_lfsr >> 23; } + + // caching helpers + void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache); + + // compute the phase step, given a PM value + uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm); + + // log a key-on event + std::string log_keyon(uint32_t choffs, uint32_t opoffs); + + // set the instrument data + void set_instrument_data(uint8_t const *data) + { + std::copy_n(data, INSTDATA_SIZE, &m_instdata[0]); + } + + // system-wide registers + uint32_t rhythm_enable() const { return byte(0x0e, 5, 1); } + uint32_t rhythm_keyon() const { return byte(0x0e, 4, 0); } + uint32_t test() const { return byte(0x0f, 0, 8); } + uint32_t waveform_enable() const { return 1; } + uint32_t timer_a_value() const { return 0; } + uint32_t timer_b_value() const { return 0; } + uint32_t status_mask() const { return 0; } + uint32_t irq_reset() const { return 0; } + uint32_t reset_timer_b() const { return 0; } + uint32_t reset_timer_a() const { return 0; } + uint32_t enable_timer_b() const { return 0; } + uint32_t enable_timer_a() const { return 0; } + uint32_t load_timer_b() const { return 0; } + uint32_t load_timer_a() const { return 0; } + uint32_t csm() const { return 0; } + + // per-channel registers + uint32_t ch_block_freq(uint32_t choffs) const { return word(0x20, 0, 4, 0x10, 0, 8, choffs); } + uint32_t ch_sustain(uint32_t choffs) const { return byte(0x20, 5, 1, choffs); } + uint32_t ch_total_level(uint32_t choffs) const { return instchbyte(0x02, 0, 6, choffs); } + uint32_t ch_feedback(uint32_t choffs) const { return instchbyte(0x03, 0, 3, choffs); } + uint32_t ch_algorithm(uint32_t choffs) const { return 0; } + uint32_t ch_instrument(uint32_t choffs) const { return byte(0x30, 4, 4, choffs); } + uint32_t ch_output_any(uint32_t choffs) const { return 1; } + uint32_t ch_output_0(uint32_t choffs) const { return !is_rhythm(choffs); } + uint32_t ch_output_1(uint32_t choffs) const { return is_rhythm(choffs); } + uint32_t ch_output_2(uint32_t choffs) const { return 0; } + uint32_t ch_output_3(uint32_t choffs) const { return 0; } + + // per-operator registers + uint32_t op_lfo_am_enable(uint32_t opoffs) const { return instopbyte(0x00, 7, 1, opoffs); } + uint32_t op_lfo_pm_enable(uint32_t opoffs) const { return instopbyte(0x00, 6, 1, opoffs); } + uint32_t op_eg_sustain(uint32_t opoffs) const { return instopbyte(0x00, 5, 1, opoffs); } + uint32_t op_ksr(uint32_t opoffs) const { return instopbyte(0x00, 4, 1, opoffs); } + uint32_t op_multiple(uint32_t opoffs) const { return instopbyte(0x00, 0, 4, opoffs); } + uint32_t op_ksl(uint32_t opoffs) const { return instopbyte(0x02, 6, 2, opoffs); } + uint32_t op_waveform(uint32_t opoffs) const { return instchbyte(0x03, 3 + bitfield(opoffs, 0), 1, opoffs >> 1); } + uint32_t op_attack_rate(uint32_t opoffs) const { return instopbyte(0x04, 4, 4, opoffs); } + uint32_t op_decay_rate(uint32_t opoffs) const { return instopbyte(0x04, 0, 4, opoffs); } + uint32_t op_sustain_level(uint32_t opoffs) const { return instopbyte(0x06, 4, 4, opoffs); } + uint32_t op_release_rate(uint32_t opoffs) const { return instopbyte(0x06, 0, 4, opoffs); } + uint32_t op_volume(uint32_t opoffs) const { return byte(0x30, 4 * bitfield(~opoffs, 0), 4, opoffs >> 1); } + +private: + // return a bitfield extracted from a byte + uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const + { + return bitfield(m_regdata[offset + extra_offset], start, count); + } + + // return a bitfield extracted from a pair of bytes, MSBs listed first + uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const + { + return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset); + } + + // helpers to read from instrument channel/operator data + uint32_t instchbyte(uint32_t offset, uint32_t start, uint32_t count, uint32_t choffs) const { return bitfield(m_chinst[choffs][offset], start, count); } + uint32_t instopbyte(uint32_t offset, uint32_t start, uint32_t count, uint32_t opoffs) const { return bitfield(m_opinst[opoffs][offset], start, count); } + + // helper to determine if the this channel is an active rhythm channel + bool is_rhythm(uint32_t choffs) const + { + return rhythm_enable() && choffs >= 6; + } + + // internal state + uint16_t m_lfo_am_counter; // LFO AM counter + uint16_t m_lfo_pm_counter; // LFO PM counter + uint32_t m_noise_lfsr; // noise LFSR state + uint8_t m_lfo_am; // current LFO AM value + uint8_t const *m_chinst[CHANNELS]; // pointer to instrument data for each channel + uint8_t const *m_opinst[OPERATORS]; // pointer to instrument data for each operator + uint8_t m_regdata[REGISTERS]; // register data + uint8_t m_instdata[INSTDATA_SIZE]; // instrument data + uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms +}; + + + +//********************************************************* +// OPL IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ym3526 + +class ym3526 +{ +public: + using fm_engine = fm_engine_base; + using output_data = fm_engine::output_data; + static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS; + + // constructor + ym3526(ymfm_interface &intf); + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate samples of sound + void generate(output_data *output, uint32_t numsamples = 1); +protected: + // internal state + uint8_t m_address; // address register + fm_engine m_fm; // core FM engine +}; + + +// ======================> y8950 + +class y8950 +{ +public: + using fm_engine = fm_engine_base; + using output_data = fm_engine::output_data; + static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS; + + static constexpr uint8_t STATUS_ADPCM_B_PLAYING = 0x01; + static constexpr uint8_t STATUS_ADPCM_B_BRDY = 0x08; + static constexpr uint8_t STATUS_ADPCM_B_EOS = 0x10; + static constexpr uint8_t ALL_IRQS = STATUS_ADPCM_B_BRDY | STATUS_ADPCM_B_EOS | fm_engine::STATUS_TIMERA | fm_engine::STATUS_TIMERB; + + // constructor + y8950(ymfm_interface &intf); + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read_data(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate samples of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal state + uint8_t m_address; // address register + uint8_t m_io_ddr; // data direction register for I/O + fm_engine m_fm; // core FM engine + adpcm_b_engine m_adpcm_b; // ADPCM-B engine +}; + + + +//********************************************************* +// OPL2 IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ym3812 + +class ym3812 +{ +public: + using fm_engine = fm_engine_base; + using output_data = fm_engine::output_data; + static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS; + + // constructor + ym3812(ymfm_interface &intf); + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate samples of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal state + uint8_t m_address; // address register + fm_engine m_fm; // core FM engine +}; + + + +//********************************************************* +// OPL3 IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ymf262 + +class ymf262 +{ +public: + using fm_engine = fm_engine_base; + using output_data = fm_engine::output_data; + static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS; + + // constructor + ymf262(ymfm_interface &intf); + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write_address_hi(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate samples of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal state + uint16_t m_address; // address register + fm_engine m_fm; // core FM engine +}; + + +// ======================> ymf289b + +class ymf289b +{ + static constexpr uint8_t STATUS_BUSY_FLAGS = 0x05; + +public: + using fm_engine = fm_engine_base; + using output_data = fm_engine::output_data; + static constexpr uint32_t OUTPUTS = 2; + + // constructor + ymf289b(ymfm_interface &intf); + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read_data(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write_address_hi(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate samples of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal helpers + bool ymf289b_mode() { return ((m_fm.regs().read(0x105) & 0x04) != 0); } + + // internal state + uint16_t m_address; // address register + fm_engine m_fm; // core FM engine +}; + + + +//********************************************************* +// OPL4 IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ymf278b + +class ymf278b +{ + // Using the nominal datasheet frequency of 33.868MHz, the output of the + // chip will be clock/768 = 44.1kHz. However, the FM engine is clocked + // internally at clock/(19*36), or 49.515kHz, so the FM output needs to + // be downsampled. We treat this as needing to clock the FM engine an + // extra tick every few samples. The exact ratio is 768/(19*36) or + // 768/684 = 192/171. So if we always clock the FM once, we'll have + // 192/171 - 1 = 21/171 left. Thus we count 21 for each sample and when + // it gets above 171, we tick an extra time. + static constexpr uint32_t FM_EXTRA_SAMPLE_THRESH = 171; + static constexpr uint32_t FM_EXTRA_SAMPLE_STEP = 192 - FM_EXTRA_SAMPLE_THRESH; + +public: + using fm_engine = fm_engine_base; + static constexpr uint32_t OUTPUTS = 6; + using output_data = ymfm_output; + + static constexpr uint8_t STATUS_BUSY = 0x01; + static constexpr uint8_t STATUS_LD = 0x02; + + // constructor + ymf278b(ymfm_interface &intf); + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return input_clock / 768; } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read_data_pcm(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write_address_hi(uint8_t data); + void write_address_pcm(uint8_t data); + void write_data_pcm(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate samples of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal state + uint16_t m_address; // address register + uint32_t m_fm_pos; // FM resampling position + uint32_t m_load_remaining; // how many more samples until LD flag clears + bool m_next_status_id; // flag to track which status ID to return + fm_engine m_fm; // core FM engine + pcm_engine m_pcm; // core PCM engine +}; + + + +//********************************************************* +// OPLL IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> opll_base + +class opll_base +{ +public: + using fm_engine = fm_engine_base; + using output_data = fm_engine::output_data; + static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS; + + // constructor + opll_base(ymfm_interface &intf, uint8_t const *data); + + // configuration + void set_instrument_data(uint8_t const *data) { m_fm.regs().set_instrument_data(data); } + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access -- doesn't really have any, but provide these for consistency + uint8_t read_status() { return 0x00; } + uint8_t read(uint32_t offset) { return 0x00; } + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate samples of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal state + uint8_t m_address; // address register + fm_engine m_fm; // core FM engine +}; + + +// ======================> ym2413 + +class ym2413 : public opll_base +{ +public: + // constructor + ym2413(ymfm_interface &intf, uint8_t const *instrument_data = nullptr); + +private: + // internal state + static uint8_t const s_default_instruments[]; +}; + + +// ======================> ym2413 + +class ym2423 : public opll_base +{ +public: + // constructor + ym2423(ymfm_interface &intf, uint8_t const *instrument_data = nullptr); + +private: + // internal state + static uint8_t const s_default_instruments[]; +}; + + +// ======================> ymf281 + +class ymf281 : public opll_base +{ +public: + // constructor + ymf281(ymfm_interface &intf, uint8_t const *instrument_data = nullptr); + +private: + // internal state + static uint8_t const s_default_instruments[]; +}; + + +// ======================> ds1001 + +class ds1001 : public opll_base +{ +public: + // constructor + ds1001(ymfm_interface &intf, uint8_t const *instrument_data = nullptr); + +private: + // internal state + static uint8_t const s_default_instruments[]; +}; + +} + +#endif // YMFM_OPL_H diff --git a/src/sound/ymfm/ymfm_opm.cpp b/src/sound/ymfm/ymfm_opm.cpp new file mode 100644 index 000000000..544bbe89a --- /dev/null +++ b/src/sound/ymfm/ymfm_opm.cpp @@ -0,0 +1,539 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "ymfm_opm.h" +#include "ymfm_fm.ipp" + +namespace ymfm +{ + +//********************************************************* +// OPM REGISTERS +//********************************************************* + +//------------------------------------------------- +// opm_registers - constructor +//------------------------------------------------- + +opm_registers::opm_registers() : + m_lfo_counter(0), + m_noise_lfsr(1), + m_noise_counter(0), + m_noise_state(0), + m_noise_lfo(0), + m_lfo_am(0) +{ + // create the waveforms + for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) + m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15); + + // create the LFO waveforms; AM in the low 8 bits, PM in the upper 8 + // waveforms are adjusted to match the pictures in the application manual + for (uint32_t index = 0; index < LFO_WAVEFORM_LENGTH; index++) + { + // waveform 0 is a sawtooth + uint8_t am = index ^ 0xff; + int8_t pm = int8_t(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); + 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); + m_lfo_waveform[2][index] = am | (pm << 8); + + // waveform 3 is noise; it is filled in dynamically + m_lfo_waveform[3][index] = 0; + } +} + + +//------------------------------------------------- +// reset - reset to initial state +//------------------------------------------------- + +void opm_registers::reset() +{ + std::fill_n(&m_regdata[0], REGISTERS, 0); + + // enable output on both channels by default + m_regdata[0x20] = m_regdata[0x21] = m_regdata[0x22] = m_regdata[0x23] = 0xc0; + m_regdata[0x24] = m_regdata[0x25] = m_regdata[0x26] = m_regdata[0x27] = 0xc0; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void opm_registers::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_lfo_counter); + state.save_restore(m_lfo_am); + state.save_restore(m_noise_lfsr); + state.save_restore(m_noise_counter); + state.save_restore(m_noise_state); + state.save_restore(m_noise_lfo); + state.save_restore(m_regdata); +} + + +//------------------------------------------------- +// operator_map - return an array of operator +// indices for each channel; for OPM this is fixed +//------------------------------------------------- + +void opm_registers::operator_map(operator_mapping &dest) const +{ + // Note that the channel index order is 0,2,1,3, so we bitswap the index. + // + // This is because the order in the map is: + // carrier 1, carrier 2, modulator 1, modulator 2 + // + // But when wiring up the connections, the more natural order is: + // carrier 1, modulator 1, carrier 2, modulator 2 + static const operator_mapping s_fixed_map = + { { + operator_list( 0, 16, 8, 24 ), // Channel 0 operators + operator_list( 1, 17, 9, 25 ), // Channel 1 operators + operator_list( 2, 18, 10, 26 ), // Channel 2 operators + operator_list( 3, 19, 11, 27 ), // Channel 3 operators + operator_list( 4, 20, 12, 28 ), // Channel 4 operators + operator_list( 5, 21, 13, 29 ), // Channel 5 operators + operator_list( 6, 22, 14, 30 ), // Channel 6 operators + operator_list( 7, 23, 15, 31 ), // Channel 7 operators + } }; + dest = s_fixed_map; +} + + +//------------------------------------------------- +// write - handle writes to the register array +//------------------------------------------------- + +bool opm_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask) +{ + assert(index < REGISTERS); + + // LFO AM/PM depth are written to the same register (0x19); + // redirect the PM depth to an unused neighbor (0x1a) + if (index == 0x19) + m_regdata[index + bitfield(data, 7)] = data; + else if (index != 0x1a) + m_regdata[index] = data; + + // handle writes to the key on index + if (index == 0x08) + { + channel = bitfield(data, 0, 3); + opmask = bitfield(data, 3, 4); + return true; + } + return false; +} + + +//------------------------------------------------- +// clock_noise_and_lfo - clock the noise and LFO, +// handling clock division, depth, and waveform +// computations +//------------------------------------------------- + +int32_t opm_registers::clock_noise_and_lfo() +{ + // base noise frequency is measured at 2x 1/2 FM frequency; this + // means each tick counts as two steps against the noise counter + uint32_t freq = noise_frequency(); + for (int rep = 0; rep < 2; rep++) + { + // evidence seems to suggest the LFSR is clocked continually and just + // sampled at the noise frequency for output purposes; note that the + // low 8 bits are the most recent 8 bits of history while bits 8-24 + // contain the 17 bit LFSR state + m_noise_lfsr <<= 1; + m_noise_lfsr |= bitfield(m_noise_lfsr, 17) ^ bitfield(m_noise_lfsr, 14) ^ 1; + + // compare against the frequency and latch when we exceed it + if (m_noise_counter++ >= freq) + { + m_noise_counter = 0; + m_noise_state = bitfield(m_noise_lfsr, 17); + } + } + + // treat the rate as a 4.4 floating-point step value with implied + // leading 1; this matches exactly the frequencies in the application + // manual, though it might not be implemented exactly this way on chip + uint32_t rate = lfo_rate(); + m_lfo_counter += (0x10 | bitfield(rate, 0, 4)) << bitfield(rate, 4, 4); + + // bit 1 of the test register is officially undocumented but has been + // discovered to hold the LFO in reset while active + if (lfo_reset()) + m_lfo_counter = 0; + + // now pull out the non-fractional LFO value + uint32_t lfo = bitfield(m_lfo_counter, 22, 8); + + // fill in the noise entry 1 ahead of our current position; this + // ensures the current value remains stable for a full LFO clock + // and effectively latches the running value when the LFO advances + uint32_t lfo_noise = bitfield(m_noise_lfsr, 17, 8); + m_lfo_waveform[3][(lfo + 1) & 0xff] = lfo_noise | (lfo_noise << 8); + + // fetch the AM/PM values based on the waveform; AM is unsigned and + // encoded in the low 8 bits, while PM signed and encoded in the upper + // 8 bits + int32_t ampm = m_lfo_waveform[lfo_waveform()][lfo]; + + // apply depth to the AM value and store for later + m_lfo_am = ((ampm & 0xff) * lfo_am_depth()) >> 7; + + // apply depth to the PM value and return it + return ((ampm >> 8) * int32_t(lfo_pm_depth())) >> 7; +} + + +//------------------------------------------------- +// lfo_am_offset - return the AM offset from LFO +// for the given channel +//------------------------------------------------- + +uint32_t opm_registers::lfo_am_offset(uint32_t choffs) const +{ + // OPM maps AM quite differently from OPN + + // shift value for AM sensitivity is [*, 0, 1, 2], + // mapping to values of [0, 23.9, 47.8, and 95.6dB] + uint32_t am_sensitivity = ch_lfo_am_sens(choffs); + if (am_sensitivity == 0) + return 0; + + // QUESTION: see OPN note below for the dB range mapping; it applies + // here as well + + // raw LFO AM value on OPM is 0-FF, which is already a factor of 2 + // larger than the OPN below, putting our staring point at 2x theirs; + // this works out since our minimum is 2x their maximum + return m_lfo_am << (am_sensitivity - 1); +} + + +//------------------------------------------------- +// cache_operator_data - fill the operator cache +// with prefetched data +//------------------------------------------------- + +void opm_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache) +{ + // set up the easy stuff + cache.waveform = &m_waveform[0][0]; + + // get frequency from the channel + uint32_t block_freq = cache.block_freq = ch_block_freq(choffs); + + // compute the keycode: block_freq is: + // + // BBBCCCCFFFFFF + // ^^^^^ + // + // the 5-bit keycode is just the top 5 bits (block + top 2 bits + // of the key code) + uint32_t keycode = bitfield(block_freq, 8, 5); + + // detune adjustment + cache.detune = detune_adjustment(op_detune(opoffs), keycode); + + // multiple value, as an x.1 value (0 means 0.5) + cache.multiple = op_multiple(opoffs) * 2; + if (cache.multiple == 0) + cache.multiple = 1; + + // phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on + // block_freq, detune, and multiple, so compute it after we've done those + if (lfo_pm_depth() == 0 || ch_lfo_pm_sens(choffs) == 0) + cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0); + else + cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC; + + // total level, scaled by 8 + cache.total_level = op_total_level(opoffs) << 3; + + // 4-bit sustain level, but 15 means 31 so effectively 5 bits + cache.eg_sustain = op_sustain_level(opoffs); + cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10; + cache.eg_sustain <<= 5; + + // determine KSR adjustment for enevlope rates + uint32_t ksrval = keycode >> (op_ksr(opoffs) ^ 3); + cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_SUSTAIN] = effective_rate(op_sustain_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_RELEASE] = effective_rate(op_release_rate(opoffs) * 4 + 2, ksrval); +} + + +//------------------------------------------------- +// compute_phase_step - compute the phase step +//------------------------------------------------- + +uint32_t opm_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm) +{ + // OPM logic is rather unique here, due to extra detune + // and the use of key codes (not to be confused with keycode) + + // start with coarse detune delta; table uses cents value from + // manual, converted into 1/64ths + static const int16_t s_detune2_delta[4] = { 0, (600*64+50)/100, (781*64+50)/100, (950*64+50)/100 }; + int32_t delta = s_detune2_delta[op_detune2(opoffs)]; + + // add in the PM delta + uint32_t pm_sensitivity = ch_lfo_pm_sens(choffs); + if (pm_sensitivity != 0) + { + // raw PM value is -127..128 which is +/- 200 cents + // manual gives these magnitudes in cents: + // 0, +/-5, +/-10, +/-20, +/-50, +/-100, +/-400, +/-700 + // this roughly corresponds to shifting the 200-cent value: + // 0 >> 5, >> 4, >> 3, >> 2, >> 1, << 1, << 2 + if (pm_sensitivity < 6) + delta += lfo_raw_pm >> (6 - pm_sensitivity); + else + delta += lfo_raw_pm << (pm_sensitivity - 5); + } + + // apply delta and convert to a frequency number + uint32_t phase_step = opm_key_code_to_phase_step(cache.block_freq, delta); + + // apply detune based on the keycode + phase_step += cache.detune; + + // apply frequency multiplier (which is cached as an x.1 value) + return (phase_step * cache.multiple) >> 1; +} + + +//------------------------------------------------- +// log_keyon - log a key-on event +//------------------------------------------------- + +std::string opm_registers::log_keyon(uint32_t choffs, uint32_t opoffs) +{ + uint32_t chnum = choffs; + uint32_t opnum = opoffs; + + char buffer[256]; + char *end = &buffer[0]; + + end += sprintf(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), + op_detune(opoffs), + ch_feedback(choffs), + ch_algorithm(choffs), + op_multiple(opoffs), + op_total_level(opoffs), + op_ksr(opoffs), + op_attack_rate(opoffs), + op_decay_rate(opoffs), + op_sustain_rate(opoffs), + op_release_rate(opoffs), + op_sustain_level(opoffs), + ch_output_0(choffs) ? 'L' : '-', + ch_output_1(choffs) ? 'R' : '-'); + + bool am = (lfo_am_depth() != 0 && ch_lfo_am_sens(choffs) != 0 && op_lfo_am_enable(opoffs) != 0); + if (am) + end += sprintf(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 += sprintf(end, " pm=%u/%02X", ch_lfo_pm_sens(choffs), lfo_pm_depth()); + if (am || pm) + end += sprintf(end, " lfo=%02X/%c", lfo_rate(), "WQTN"[lfo_waveform()]); + if (noise_enable() && opoffs == 31) + end += sprintf(end, " noise=1"); + + return buffer; +} + + + +//********************************************************* +// YM2151 +//********************************************************* + +//------------------------------------------------- +// ym2151 - constructor +//------------------------------------------------- + +ym2151::ym2151(ymfm_interface &intf, opm_variant variant) : + m_variant(variant), + m_address(0), + m_fm(intf) +{ +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ym2151::reset() +{ + // reset the engines + m_fm.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ym2151::save_restore(ymfm_saved_state &state) +{ + m_fm.save_restore(state); + state.save_restore(m_address); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ym2151::read_status() +{ + uint8_t result = m_fm.status(); + if (m_fm.intf().ymfm_is_busy()) + result |= fm_engine::STATUS_BUSY; + return result; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ym2151::read(uint32_t offset) +{ + uint8_t result = 0xff; + switch (offset & 1) + { + case 0: // data port (unused) + debug::log_unexpected_read_write("Unexpected read from YM2151 offset %d\n", offset & 3); + break; + + case 1: // status port, YM2203 compatible + result = read_status(); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ym2151::write_address(uint8_t data) +{ + // just set the address + m_address = data; +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym2151::write_data(uint8_t data) +{ + // write the FM register + m_fm.write(m_address, data); + + // special cases + if (m_address == 0x1b) + { + // writes to register 0x1B send the upper 2 bits to the output lines + m_fm.intf().ymfm_external_write(ACCESS_IO, 0, data >> 6); + } + + // mark busy for a bit + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym2151::write(uint32_t offset, uint8_t data) +{ + switch (offset & 1) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void ym2151::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // update the FM content; OPM is full 14-bit with no intermediate clipping + m_fm.output(output->clear(), 0, 32767, fm_engine::ALL_CHANNELS); + + // YM2151 uses an external DAC (YM3012) with mantissa/exponent format + // convert to 10.3 floating point value and back to simulate truncation + output->roundtrip_fp(); + } +} + +} diff --git a/src/sound/ymfm/ymfm_opm.h b/src/sound/ymfm/ymfm_opm.h new file mode 100644 index 000000000..b126135d4 --- /dev/null +++ b/src/sound/ymfm/ymfm_opm.h @@ -0,0 +1,322 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_OPM_H +#define YMFM_OPM_H + +#pragma once + +#include "ymfm.h" +#include "ymfm_fm.h" + +namespace ymfm +{ + +//********************************************************* +// REGISTER CLASSES +//********************************************************* + +// ======================> opm_registers + +// +// OPM register map: +// +// System-wide registers: +// 01 xxxxxx-x Test register +// ------x- LFO reset +// 08 -x------ Key on/off operator 4 +// --x----- Key on/off operator 3 +// ---x---- Key on/off operator 2 +// ----x--- Key on/off operator 1 +// -----xxx Channel select +// 0F x------- Noise enable +// ---xxxxx Noise frequency +// 10 xxxxxxxx Timer A value (upper 8 bits) +// 11 ------xx Timer A value (lower 2 bits) +// 12 xxxxxxxx Timer B value +// 14 x------- CSM mode +// --x----- Reset timer B +// ---x---- Reset timer A +// ----x--- Enable timer B +// -----x-- Enable timer A +// ------x- Load timer B +// -------x Load timer A +// 18 xxxxxxxx LFO frequency +// 19 0xxxxxxx AM LFO depth +// 1xxxxxxx PM LFO depth +// 1B xx------ CT (2 output data lines) +// ------xx LFO waveform +// +// Per-channel registers (channel in address bits 0-2) +// 20-27 x------- Pan right +// -x------ Pan left +// --xxx--- Feedback level for operator 1 (0-7) +// -----xxx Operator connection algorithm (0-7) +// 28-2F -xxxxxxx Key code +// 30-37 xxxxxx-- Key fraction +// 38-3F -xxx---- LFO PM sensitivity +// ------xx LFO AM shift +// +// Per-operator registers (channel in address bits 0-2, operator in bits 3-4) +// 40-5F -xxx---- Detune value (0-7) +// ----xxxx Multiple value (0-15) +// 60-7F -xxxxxxx Total level (0-127) +// 80-9F xx------ Key scale rate (0-3) +// ---xxxxx Attack rate (0-31) +// A0-BF x------- LFO AM enable +// ---xxxxx Decay rate (0-31) +// C0-DF xx------ Detune 2 value (0-3) +// ---xxxxx Sustain rate (0-31) +// E0-FF xxxx---- Sustain level (0-15) +// ----xxxx Release rate (0-15) +// +// Internal (fake) registers: +// 1A -xxxxxxx PM depth +// + +class opm_registers : public fm_registers_base +{ + // LFO waveforms are 256 entries long + static constexpr uint32_t LFO_WAVEFORM_LENGTH = 256; + +public: + // constants + static constexpr uint32_t OUTPUTS = 2; + static constexpr uint32_t CHANNELS = 8; + static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1; + static constexpr uint32_t OPERATORS = CHANNELS * 4; + static constexpr uint32_t WAVEFORMS = 1; + static constexpr uint32_t REGISTERS = 0x100; + static constexpr uint32_t DEFAULT_PRESCALE = 2; + static constexpr uint32_t EG_CLOCK_DIVIDER = 3; + static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS; + static constexpr uint32_t REG_MODE = 0x14; + static constexpr uint8_t STATUS_TIMERA = 0x01; + static constexpr uint8_t STATUS_TIMERB = 0x02; + static constexpr uint8_t STATUS_BUSY = 0x80; + static constexpr uint8_t STATUS_IRQ = 0; + + // constructor + opm_registers(); + + // reset to initial state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // map channel number to register offset + static constexpr uint32_t channel_offset(uint32_t chnum) + { + assert(chnum < CHANNELS); + return chnum; + } + + // map operator number to register offset + static constexpr uint32_t operator_offset(uint32_t opnum) + { + assert(opnum < OPERATORS); + return opnum; + } + + // return an array of operator indices for each channel + struct operator_mapping { uint32_t chan[CHANNELS]; }; + void operator_map(operator_mapping &dest) const; + + // handle writes to the register array + bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask); + + // clock the noise and LFO, if present, returning LFO PM value + int32_t clock_noise_and_lfo(); + + // return the AM offset from LFO for the given channel + uint32_t lfo_am_offset(uint32_t choffs) const; + + // return the current noise state, gated by the noise clock + uint32_t noise_state() const { return m_noise_state; } + + // caching helpers + void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache); + + // compute the phase step, given a PM value + uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm); + + // log a key-on event + std::string log_keyon(uint32_t choffs, uint32_t opoffs); + + // system-wide registers + uint32_t test() const { return byte(0x01, 0, 8); } + uint32_t lfo_reset() const { return byte(0x01, 1, 1); } + uint32_t noise_frequency() const { return byte(0x0f, 0, 5) ^ 0x1f; } + uint32_t noise_enable() const { return byte(0x0f, 7, 1); } + uint32_t timer_a_value() const { return word(0x10, 0, 8, 0x11, 0, 2); } + uint32_t timer_b_value() const { return byte(0x12, 0, 8); } + uint32_t csm() const { return byte(0x14, 7, 1); } + uint32_t reset_timer_b() const { return byte(0x14, 5, 1); } + uint32_t reset_timer_a() const { return byte(0x14, 4, 1); } + uint32_t enable_timer_b() const { return byte(0x14, 3, 1); } + uint32_t enable_timer_a() const { return byte(0x14, 2, 1); } + uint32_t load_timer_b() const { return byte(0x14, 1, 1); } + uint32_t load_timer_a() const { return byte(0x14, 0, 1); } + uint32_t lfo_rate() const { return byte(0x18, 0, 8); } + uint32_t lfo_am_depth() const { return byte(0x19, 0, 7); } + uint32_t lfo_pm_depth() const { return byte(0x1a, 0, 7); } + uint32_t output_bits() const { return byte(0x1b, 6, 2); } + uint32_t lfo_waveform() const { return byte(0x1b, 0, 2); } + + // per-channel registers + uint32_t ch_output_any(uint32_t choffs) const { return byte(0x20, 6, 2, choffs); } + uint32_t ch_output_0(uint32_t choffs) const { return byte(0x20, 6, 1, choffs); } + uint32_t ch_output_1(uint32_t choffs) const { return byte(0x20, 7, 1, choffs); } + uint32_t ch_output_2(uint32_t choffs) const { return 0; } + uint32_t ch_output_3(uint32_t choffs) const { return 0; } + uint32_t ch_feedback(uint32_t choffs) const { return byte(0x20, 3, 3, choffs); } + uint32_t ch_algorithm(uint32_t choffs) const { return byte(0x20, 0, 3, choffs); } + uint32_t ch_block_freq(uint32_t choffs) const { return word(0x28, 0, 7, 0x30, 2, 6, choffs); } + uint32_t ch_lfo_pm_sens(uint32_t choffs) const { return byte(0x38, 4, 3, choffs); } + uint32_t ch_lfo_am_sens(uint32_t choffs) const { return byte(0x38, 0, 2, choffs); } + + // per-operator registers + uint32_t op_detune(uint32_t opoffs) const { return byte(0x40, 4, 3, opoffs); } + uint32_t op_multiple(uint32_t opoffs) const { return byte(0x40, 0, 4, opoffs); } + uint32_t op_total_level(uint32_t opoffs) const { return byte(0x60, 0, 7, opoffs); } + uint32_t op_ksr(uint32_t opoffs) const { return byte(0x80, 6, 2, opoffs); } + uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x80, 0, 5, opoffs); } + uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0xa0, 7, 1, opoffs); } + uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0xa0, 0, 5, opoffs); } + uint32_t op_detune2(uint32_t opoffs) const { return byte(0xc0, 6, 2, opoffs); } + uint32_t op_sustain_rate(uint32_t opoffs) const { return byte(0xc0, 0, 5, opoffs); } + uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0xe0, 4, 4, opoffs); } + uint32_t op_release_rate(uint32_t opoffs) const { return byte(0xe0, 0, 4, opoffs); } + +protected: + // return a bitfield extracted from a byte + uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const + { + return bitfield(m_regdata[offset + extra_offset], start, count); + } + + // return a bitfield extracted from a pair of bytes, MSBs listed first + uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const + { + return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset); + } + + // internal state + uint32_t m_lfo_counter; // LFO counter + uint32_t m_noise_lfsr; // noise LFSR state + uint8_t m_noise_counter; // noise counter + uint8_t m_noise_state; // latched noise state + uint8_t m_noise_lfo; // latched LFO noise value + uint8_t m_lfo_am; // current LFO AM value + uint8_t m_regdata[REGISTERS]; // register data + int16_t m_lfo_waveform[4][LFO_WAVEFORM_LENGTH]; // LFO waveforms; AM in low 8, PM in upper 8 + uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms +}; + + + +//********************************************************* +// OPM IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ym2151 + +class ym2151 +{ +public: + using fm_engine = fm_engine_base; + using output_data = fm_engine::output_data; + static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS; + + // constructor + ym2151(ymfm_interface &intf) : ym2151(intf, VARIANT_YM2151) { } + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // variants + enum opm_variant + { + VARIANT_YM2151, + VARIANT_YM2164 + }; + + // internal constructor + ym2151(ymfm_interface &intf, opm_variant variant); + + // internal state + opm_variant m_variant; // chip variant + uint8_t m_address; // address register + fm_engine m_fm; // core FM engine +}; + + + +//********************************************************* +// OPP IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ym2164 + +// the YM2164 is almost 100% functionally identical to the YM2151, except +// it apparently has some mystery registers in the 00-07 range, and timer +// B's frequency is half that of the 2151 +class ym2164 : public ym2151 +{ +public: + // constructor + ym2164(ymfm_interface &intf) : ym2151(intf, VARIANT_YM2164) { } +}; + +} + + +#endif // YMFM_OPM_H diff --git a/src/sound/ymfm/ymfm_opn.cpp b/src/sound/ymfm/ymfm_opn.cpp new file mode 100644 index 000000000..053ad9770 --- /dev/null +++ b/src/sound/ymfm/ymfm_opn.cpp @@ -0,0 +1,2488 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "ymfm_opn.h" +#include "ymfm_fm.ipp" + +namespace ymfm +{ + +//********************************************************* +// OPN/OPNA REGISTERS +//********************************************************* + +//------------------------------------------------- +// opn_registers_base - constructor +//------------------------------------------------- + +template +opn_registers_base::opn_registers_base() : + m_lfo_counter(0), + m_lfo_am(0) +{ + // create the waveforms + for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) + m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15); +} + + +//------------------------------------------------- +// reset - reset to initial state +//------------------------------------------------- + +template +void opn_registers_base::reset() +{ + std::fill_n(&m_regdata[0], REGISTERS, 0); + if (IsOpnA) + { + // enable output on both channels by default + m_regdata[0xb4] = m_regdata[0xb5] = m_regdata[0xb6] = 0xc0; + m_regdata[0x1b4] = m_regdata[0x1b5] = m_regdata[0x1b6] = 0xc0; + } +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +template +void opn_registers_base::save_restore(ymfm_saved_state &state) +{ + if (IsOpnA) + { + state.save_restore(m_lfo_counter); + state.save_restore(m_lfo_am); + } + state.save_restore(m_regdata); +} + + +//------------------------------------------------- +// operator_map - return an array of operator +// indices for each channel; for OPN this is fixed +//------------------------------------------------- + +template<> +void opn_registers_base::operator_map(operator_mapping &dest) const +{ + // Note that the channel index order is 0,2,1,3, so we bitswap the index. + // + // This is because the order in the map is: + // carrier 1, carrier 2, modulator 1, modulator 2 + // + // But when wiring up the connections, the more natural order is: + // carrier 1, modulator 1, carrier 2, modulator 2 + static const operator_mapping s_fixed_map = + { { + operator_list( 0, 6, 3, 9 ), // Channel 0 operators + operator_list( 1, 7, 4, 10 ), // Channel 1 operators + operator_list( 2, 8, 5, 11 ), // Channel 2 operators + } }; + dest = s_fixed_map; +} + +template<> +void opn_registers_base::operator_map(operator_mapping &dest) const +{ + // Note that the channel index order is 0,2,1,3, so we bitswap the index. + // + // This is because the order in the map is: + // carrier 1, carrier 2, modulator 1, modulator 2 + // + // But when wiring up the connections, the more natural order is: + // carrier 1, modulator 1, carrier 2, modulator 2 + static const operator_mapping s_fixed_map = + { { + operator_list( 0, 6, 3, 9 ), // Channel 0 operators + operator_list( 1, 7, 4, 10 ), // Channel 1 operators + operator_list( 2, 8, 5, 11 ), // Channel 2 operators + operator_list( 12, 18, 15, 21 ), // Channel 3 operators + operator_list( 13, 19, 16, 22 ), // Channel 4 operators + operator_list( 14, 20, 17, 23 ), // Channel 5 operators + } }; + dest = s_fixed_map; +} + + +//------------------------------------------------- +// write - handle writes to the register array +//------------------------------------------------- + +template +bool opn_registers_base::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask) +{ + assert(index < REGISTERS); + + // writes in the 0xa0-af/0x1a0-af region are handled as latched pairs + // borrow unused registers 0xb8-bf/0x1b8-bf as temporary holding locations + if ((index & 0xf0) == 0xa0) + { + if (bitfield(index, 0, 2) == 3) + return false; + + uint32_t latchindex = 0xb8 | bitfield(index, 3); + if (IsOpnA) + latchindex |= index & 0x100; + + // writes to the upper half just latch (only low 6 bits matter) + if (bitfield(index, 2)) + m_regdata[latchindex] = data | 0x80; + + // writes to the lower half only commit if the latch is there + else if (bitfield(m_regdata[latchindex], 7)) + { + m_regdata[index] = data; + m_regdata[index | 4] = m_regdata[latchindex] & 0x3f; + m_regdata[latchindex] = 0; + } + return false; + } + else if ((index & 0xf8) == 0xb8) + { + // registers 0xb8-0xbf are used internally + return false; + } + + // everything else is normal + m_regdata[index] = data; + + // handle writes to the key on index + if (index == 0x28) + { + channel = bitfield(data, 0, 2); + if (channel == 3) + return false; + if (IsOpnA) + channel += bitfield(data, 2, 1) * 3; + opmask = bitfield(data, 4, 4); + return true; + } + return false; +} + + +//------------------------------------------------- +// clock_noise_and_lfo - clock the noise and LFO, +// handling clock division, depth, and waveform +// computations +//------------------------------------------------- + +template +int32_t opn_registers_base::clock_noise_and_lfo() +{ + // OPN has no noise generation + + // if LFO not enabled (not present on OPN), quick exit with 0s + if (!IsOpnA || !lfo_enable()) + { + m_lfo_counter = 0; + + // special case: if LFO is disabled on OPNA, it basically just keeps the counter + // at 0; since position 0 gives an AM value of 0x3f, it is important to reflect + // that here; for example, MegaDrive Venom plays some notes with LFO globally + // disabled but enabling LFO on the operators, and it expects this added attenutation + m_lfo_am = IsOpnA ? 0x3f : 0x00; + return 0; + } + + // this table is based on converting the frequencies in the applications + // manual to clock dividers, based on the assumption of a 7-bit LFO value + static uint8_t const lfo_max_count[8] = { 109, 78, 72, 68, 63, 45, 9, 6 }; + uint32_t subcount = uint8_t(m_lfo_counter++); + + // when we cross the divider count, add enough to zero it and cause an + // increment at bit 8; the 7-bit value lives from bits 8-14 + if (subcount >= lfo_max_count[lfo_rate()]) + { + // note: to match the published values this should be 0x100 - subcount; + // however, tests on the hardware and nuked bear out an off-by-one + // error exists that causes the max LFO rate to be faster than published + m_lfo_counter += 0x101 - subcount; + } + + // AM value is 7 bits, staring at bit 8; grab the low 6 directly + m_lfo_am = bitfield(m_lfo_counter, 8, 6); + + // first half of the AM period (bit 6 == 0) is inverted + if (bitfield(m_lfo_counter, 8+6) == 0) + m_lfo_am ^= 0x3f; + + // PM value is 5 bits, starting at bit 10; grab the low 3 directly + int32_t pm = bitfield(m_lfo_counter, 10, 3); + + // PM is reflected based on bit 3 + if (bitfield(m_lfo_counter, 10+3)) + pm ^= 7; + + // PM is negated based on bit 4 + return bitfield(m_lfo_counter, 10+4) ? -pm : pm; +} + + +//------------------------------------------------- +// lfo_am_offset - return the AM offset from LFO +// for the given channel +//------------------------------------------------- + +template +uint32_t opn_registers_base::lfo_am_offset(uint32_t choffs) const +{ + // shift value for AM sensitivity is [7, 3, 1, 0], + // mapping to values of [0, 1.4, 5.9, and 11.8dB] + uint32_t am_shift = (1 << (ch_lfo_am_sens(choffs) ^ 3)) - 1; + + // QUESTION: max sensitivity should give 11.8dB range, but this value + // is directly added to an x.8 attenuation value, which will only give + // 126/256 or ~4.9dB range -- what am I missing? The calculation below + // matches several other emulators, including the Nuked implemenation. + + // raw LFO AM value on OPN is 0-3F, scale that up by a factor of 2 + // (giving 7 bits) before applying the final shift + return (m_lfo_am << 1) >> am_shift; +} + + +//------------------------------------------------- +// cache_operator_data - fill the operator cache +// with prefetched data +//------------------------------------------------- + +template +void opn_registers_base::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache) +{ + // set up the easy stuff + cache.waveform = &m_waveform[0][0]; + + // get frequency from the channel + uint32_t block_freq = cache.block_freq = ch_block_freq(choffs); + + // if multi-frequency mode is enabled and this is channel 2, + // fetch one of the special frequencies + if (multi_freq() && choffs == 2) + { + if (opoffs == 2) + block_freq = cache.block_freq = multi_block_freq(1); + else if (opoffs == 10) + block_freq = cache.block_freq = multi_block_freq(2); + else if (opoffs == 6) + block_freq = cache.block_freq = multi_block_freq(0); + } + + // compute the keycode: block_freq is: + // + // BBBFFFFFFFFFFF + // ^^^^??? + // + // the 5-bit keycode uses the top 4 bits plus a magic formula + // for the final bit + uint32_t keycode = bitfield(block_freq, 10, 4) << 1; + + // lowest bit is determined by a mix of next lower FNUM bits + // according to this equation from the YM2608 manual: + // + // (F11 & (F10 | F9 | F8)) | (!F11 & F10 & F9 & F8) + // + // for speed, we just look it up in a 16-bit constant + keycode |= bitfield(0xfe80, bitfield(block_freq, 7, 4)); + + // detune adjustment + cache.detune = detune_adjustment(op_detune(opoffs), keycode); + + // multiple value, as an x.1 value (0 means 0.5) + cache.multiple = op_multiple(opoffs) * 2; + if (cache.multiple == 0) + cache.multiple = 1; + + // phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on + // block_freq, detune, and multiple, so compute it after we've done those + if (!IsOpnA || lfo_enable() == 0 || ch_lfo_pm_sens(choffs) == 0) + cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0); + else + cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC; + + // total level, scaled by 8 + cache.total_level = op_total_level(opoffs) << 3; + + // 4-bit sustain level, but 15 means 31 so effectively 5 bits + cache.eg_sustain = op_sustain_level(opoffs); + cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10; + cache.eg_sustain <<= 5; + + // determine KSR adjustment for enevlope rates + uint32_t ksrval = keycode >> (op_ksr(opoffs) ^ 3); + cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_SUSTAIN] = effective_rate(op_sustain_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_RELEASE] = effective_rate(op_release_rate(opoffs) * 4 + 2, ksrval); +} + + +//------------------------------------------------- +// compute_phase_step - compute the phase step +//------------------------------------------------- + +template +uint32_t opn_registers_base::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm) +{ + // OPN phase calculation has only a single detune parameter + // and uses FNUMs instead of keycodes + + // extract frequency number (low 11 bits of block_freq) + uint32_t fnum = bitfield(cache.block_freq, 0, 11) << 1; + + // if there's a non-zero PM sensitivity, compute the adjustment + uint32_t pm_sensitivity = ch_lfo_pm_sens(choffs); + if (pm_sensitivity != 0) + { + // apply the phase adjustment based on the upper 7 bits + // of FNUM and the PM depth parameters + fnum += opn_lfo_pm_phase_adjustment(bitfield(cache.block_freq, 4, 7), pm_sensitivity, lfo_raw_pm); + + // keep fnum to 12 bits + fnum &= 0xfff; + } + + // apply block shift to compute phase step + uint32_t block = bitfield(cache.block_freq, 11, 3); + uint32_t phase_step = (fnum << block) >> 2; + + // apply detune based on the keycode + phase_step += cache.detune; + + // clamp to 17 bits in case detune overflows + // QUESTION: is this specific to the YM2612/3438? + phase_step &= 0x1ffff; + + // apply frequency multiplier (which is cached as an x.1 value) + return (phase_step * cache.multiple) >> 1; +} + + +//------------------------------------------------- +// log_keyon - log a key-on event +//------------------------------------------------- + +template +std::string opn_registers_base::log_keyon(uint32_t choffs, uint32_t opoffs) +{ + uint32_t chnum = (choffs & 3) + 3 * bitfield(choffs, 8); + uint32_t opnum = (opoffs & 15) - ((opoffs & 15) / 4) + 12 * bitfield(opoffs, 8); + + uint32_t block_freq = ch_block_freq(choffs); + if (multi_freq() && choffs == 2) + { + if (opoffs == 2) + block_freq = multi_block_freq(1); + else if (opoffs == 10) + block_freq = multi_block_freq(2); + else if (opoffs == 6) + block_freq = multi_block_freq(0); + } + + char buffer[256]; + char *end = &buffer[0]; + + end += sprintf(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), + ch_feedback(choffs), + ch_algorithm(choffs), + op_multiple(opoffs), + op_total_level(opoffs), + op_ksr(opoffs), + op_attack_rate(opoffs), + op_decay_rate(opoffs), + op_sustain_rate(opoffs), + op_release_rate(opoffs), + op_sustain_level(opoffs)); + + if (OUTPUTS > 1) + end += sprintf(end, " out=%c%c", + ch_output_0(choffs) ? 'L' : '-', + ch_output_1(choffs) ? 'R' : '-'); + if (op_ssg_eg_enable(opoffs)) + end += sprintf(end, " ssg=%X", op_ssg_eg_mode(opoffs)); + bool am = (op_lfo_am_enable(opoffs) && ch_lfo_am_sens(choffs) != 0); + if (am) + end += sprintf(end, " am=%u", ch_lfo_am_sens(choffs)); + bool pm = (ch_lfo_pm_sens(choffs) != 0); + if (pm) + end += sprintf(end, " pm=%u", ch_lfo_pm_sens(choffs)); + if (am || pm) + end += sprintf(end, " lfo=%02X", lfo_rate()); + if (multi_freq() && choffs == 2) + end += sprintf(end, " multi=1"); + + return buffer; +} + + + +//********************************************************* +// SSG RESAMPLER +//********************************************************* + +//------------------------------------------------- +// add_last - helper to add the last computed +// value to the sums, applying the given scale +//------------------------------------------------- + +template +void ssg_resampler::add_last(int32_t &sum0, int32_t &sum1, int32_t &sum2, int32_t scale) +{ + sum0 += m_last.data[0] * scale; + sum1 += m_last.data[1] * scale; + sum2 += m_last.data[2] * scale; +} + + +//------------------------------------------------- +// clock_and_add - helper to clock a new value +// and then add it to the sums, applying the +// given scale +//------------------------------------------------- + +template +void ssg_resampler::clock_and_add(int32_t &sum0, int32_t &sum1, int32_t &sum2, int32_t scale) +{ + m_ssg.clock(); + m_ssg.output(m_last); + add_last(sum0, sum1, sum2, scale); +} + + +//------------------------------------------------- +// write_to_output - helper to write the sums to +// the appropriate outputs, applying the given +// divisor to the final result +//------------------------------------------------- + +template +void ssg_resampler::write_to_output(OutputType *output, int32_t sum0, int32_t sum1, int32_t sum2, int32_t divisor) +{ + if (MixTo1) + { + // mixing to one, apply a 2/3 factor to prevent overflow + output->data[FirstOutput] = (sum0 + sum1 + sum2) * 2 / (3 * divisor); + } + else + { + // write three outputs in a row + output->data[FirstOutput + 0] = sum0 / divisor; + output->data[FirstOutput + 1] = sum1 / divisor; + output->data[FirstOutput + 2] = sum2 / divisor; + } + + // track the sample index here + m_sampindex++; +} + + +//------------------------------------------------- +// ssg_resampler - constructor +//------------------------------------------------- + +template +ssg_resampler::ssg_resampler(ssg_engine &ssg) : + m_ssg(ssg), + m_sampindex(0), + m_resampler(&ssg_resampler::resample_nop) +{ + m_last.clear(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +template +void ssg_resampler::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_sampindex); + state.save_restore(m_last.data); +} + + +//------------------------------------------------- +// configure - configure a new ratio +//------------------------------------------------- + +template +void ssg_resampler::configure(uint8_t outsamples, uint8_t srcsamples) +{ + switch (outsamples * 10 + srcsamples) + { + case 4*10 + 1: /* 4:1 */ m_resampler = &ssg_resampler::resample_n_1<4>; break; + case 2*10 + 1: /* 2:1 */ m_resampler = &ssg_resampler::resample_n_1<2>; break; + case 4*10 + 3: /* 4:3 */ m_resampler = &ssg_resampler::resample_4_3; break; + case 1*10 + 1: /* 1:1 */ m_resampler = &ssg_resampler::resample_n_1<1>; break; + case 2*10 + 3: /* 2:3 */ m_resampler = &ssg_resampler::resample_2_3; break; + case 1*10 + 3: /* 1:3 */ m_resampler = &ssg_resampler::resample_1_n<3>; break; + case 2*10 + 9: /* 2:9 */ m_resampler = &ssg_resampler::resample_2_9; break; + case 1*10 + 6: /* 1:6 */ m_resampler = &ssg_resampler::resample_1_n<6>; break; + case 0*10 + 0: /* 0:0 */ m_resampler = &ssg_resampler::resample_nop; break; + default: assert(false); break; + } +} + + +//------------------------------------------------- +// resample_n_1 - resample SSG output to the +// target at a rate of 1 SSG sample to every +// n output sample +//------------------------------------------------- + +template +template +void ssg_resampler::resample_n_1(OutputType *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + if (m_sampindex % Multiplier == 0) + { + m_ssg.clock(); + m_ssg.output(m_last); + } + write_to_output(output, m_last.data[0], m_last.data[1], m_last.data[2]); + } +} + + +//------------------------------------------------- +// resample_1_n - resample SSG output to the +// target at a rate of n SSG samples to every +// 1 output sample +//------------------------------------------------- + +template +template +void ssg_resampler::resample_1_n(OutputType *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + int32_t sum0 = 0, sum1 = 0, sum2 = 0; + for (int rep = 0; rep < Divisor; rep++) + clock_and_add(sum0, sum1, sum2); + write_to_output(output, sum0, sum1, sum2, Divisor); + } +} + + +//------------------------------------------------- +// resample_2_9 - resample SSG output to the +// target at a rate of 9 SSG samples to every +// 2 output samples +//------------------------------------------------- + +template +void ssg_resampler::resample_2_9(OutputType *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + int32_t sum0 = 0, sum1 = 0, sum2 = 0; + if (bitfield(m_sampindex, 0) != 0) + add_last(sum0, sum1, sum2, 1); + clock_and_add(sum0, sum1, sum2, 2); + clock_and_add(sum0, sum1, sum2, 2); + clock_and_add(sum0, sum1, sum2, 2); + clock_and_add(sum0, sum1, sum2, 2); + if (bitfield(m_sampindex, 0) == 0) + clock_and_add(sum0, sum1, sum2, 1); + write_to_output(output, sum0, sum1, sum2, 9); + } +} + + +//------------------------------------------------- +// resample_2_3 - resample SSG output to the +// target at a rate of 3 SSG samples to every +// 2 output samples +//------------------------------------------------- + +template +void ssg_resampler::resample_2_3(OutputType *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + int32_t sum0 = 0, sum1 = 0, sum2 = 0; + if (bitfield(m_sampindex, 0) == 0) + { + clock_and_add(sum0, sum1, sum2, 2); + clock_and_add(sum0, sum1, sum2, 1); + } + else + { + add_last(sum0, sum1, sum2, 1); + clock_and_add(sum0, sum1, sum2, 2); + } + write_to_output(output, sum0, sum1, sum2, 3); + } +} + + +//------------------------------------------------- +// resample_4_3 - resample SSG output to the +// target at a rate of 3 SSG samples to every +// 4 output samples +//------------------------------------------------- + +template +void ssg_resampler::resample_4_3(OutputType *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + int32_t sum0 = 0, sum1 = 0, sum2 = 0; + int32_t step = bitfield(m_sampindex, 0, 2); + add_last(sum0, sum1, sum2, step); + if (step != 3) + clock_and_add(sum0, sum1, sum2, 3 - step); + write_to_output(output, sum0, sum1, sum2, 3); + } +} + + +//------------------------------------------------- +// resample_nop - no-op resampler +//------------------------------------------------- + +template +void ssg_resampler::resample_nop(OutputType *output, uint32_t numsamples) +{ + // nothing to do except increment the sample index + m_sampindex += numsamples; +} + + + +//********************************************************* +// YM2203 +//********************************************************* + +//------------------------------------------------- +// ym2203 - constructor +//------------------------------------------------- + +ym2203::ym2203(ymfm_interface &intf) : + m_fidelity(OPN_FIDELITY_MAX), + m_address(0), + m_fm(intf), + m_ssg(intf), + m_ssg_resampler(m_ssg) +{ + m_last_fm.clear(); + update_prescale(m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ym2203::reset() +{ + // reset the engines + m_fm.reset(); + m_ssg.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ym2203::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + state.save_restore(m_last_fm.data); + + m_fm.save_restore(state); + m_ssg.save_restore(state); + m_ssg_resampler.save_restore(state); + + update_prescale(m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ym2203::read_status() +{ + uint8_t result = m_fm.status(); + if (m_fm.intf().ymfm_is_busy()) + result |= fm_engine::STATUS_BUSY; + return result; +} + + +//------------------------------------------------- +// read_data - read the data register +//------------------------------------------------- + +uint8_t ym2203::read_data() +{ + uint8_t result = 0; + if (m_address < 0x10) + { + // 00-0F: Read from SSG + result = m_ssg.read(m_address & 0x0f); + } + return result; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ym2203::read(uint32_t offset) +{ + uint8_t result = 0xff; + switch (offset & 1) + { + case 0: // status port + result = read_status(); + break; + + case 1: // data port (only SSG) + result = read_data(); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ym2203::write_address(uint8_t data) +{ + // just set the address + m_address = data; + + // special case: update the prescale + if (m_address >= 0x2d && m_address <= 0x2f) + { + // 2D-2F: prescaler select + if (m_address == 0x2d) + update_prescale(6); + else if (m_address == 0x2e && m_fm.clock_prescale() == 6) + update_prescale(3); + else if (m_address == 0x2f) + update_prescale(2); + } +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym2203::write_data(uint8_t data) +{ + if (m_address < 0x10) + { + // 00-0F: write to SSG + m_ssg.write(m_address & 0x0f, data); + } + else + { + // 10-FF: write to FM + m_fm.write(m_address, data); + } + + // mark busy for a bit + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym2203::write(uint32_t offset, uint8_t data) +{ + switch (offset & 1) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void ym2203::generate(output_data *output, uint32_t numsamples) +{ + // FM output is just repeated the prescale number of times; note that + // 0 is a special 1.5 case + if (m_fm_samples_per_output != 0) + { + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + if ((m_ssg_resampler.sampindex() + samp) % m_fm_samples_per_output == 0) + clock_fm(); + output->data[0] = m_last_fm.data[0]; + } + } + else + { + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + uint32_t step = (m_ssg_resampler.sampindex() + samp) % 3; + if (step == 0) + clock_fm(); + output->data[0] = m_last_fm.data[0]; + if (step == 1) + { + clock_fm(); + output->data[0] = (output->data[0] + m_last_fm.data[0]) / 2; + } + } + } + + // resample the SSG as configured + m_ssg_resampler.resample(output - numsamples, numsamples); +} + + +//------------------------------------------------- +// update_prescale - update the prescale value, +// recomputing derived values +//------------------------------------------------- + +void ym2203::update_prescale(uint8_t prescale) +{ + // tell the FM engine + m_fm.set_clock_prescale(prescale); + m_ssg.prescale_changed(); + + // Fidelity: ---- minimum ---- ---- medium ----- ---- maximum----- + // rate = clock/24 rate = clock/12 rate = clock/4 + // Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate + // 6 3:1 2:3 6:1 4:3 18:1 4:1 + // 3 1.5:1 1:3 3:1 2:3 9:1 2:1 + // 2 1:1 1:6 2:1 1:3 6:1 1:1 + + // compute the number of FM samples per output sample, and select the + // resampler function + if (m_fidelity == OPN_FIDELITY_MIN) + { + switch (prescale) + { + default: + case 6: m_fm_samples_per_output = 3; m_ssg_resampler.configure(2, 3); break; + case 3: m_fm_samples_per_output = 0; m_ssg_resampler.configure(1, 3); break; + case 2: m_fm_samples_per_output = 1; m_ssg_resampler.configure(1, 6); break; + } + } + else if (m_fidelity == OPN_FIDELITY_MED) + { + switch (prescale) + { + default: + case 6: m_fm_samples_per_output = 6; m_ssg_resampler.configure(4, 3); break; + case 3: m_fm_samples_per_output = 3; m_ssg_resampler.configure(2, 3); break; + case 2: m_fm_samples_per_output = 2; m_ssg_resampler.configure(1, 3); break; + } + } + else + { + switch (prescale) + { + default: + case 6: m_fm_samples_per_output = 18; m_ssg_resampler.configure(4, 1); break; + case 3: m_fm_samples_per_output = 9; m_ssg_resampler.configure(2, 1); break; + case 2: m_fm_samples_per_output = 6; m_ssg_resampler.configure(1, 1); break; + } + } + + // if overriding the SSG, override the configuration with the nop + // resampler to at least keep the sample index moving forward + if (m_ssg.overridden()) + m_ssg_resampler.configure(0, 0); +} + + +//------------------------------------------------- +// clock_fm - clock FM state +//------------------------------------------------- + +void ym2203::clock_fm() +{ + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // update the FM content; OPN is full 14-bit with no intermediate clipping + m_fm.output(m_last_fm.clear(), 0, 32767, fm_engine::ALL_CHANNELS); + + // convert to 10.3 floating point value for the DAC and back + m_last_fm.roundtrip_fp(); +} + + + +//********************************************************* +// YM2608 +//********************************************************* + +//------------------------------------------------- +// ym2608 - constructor +//------------------------------------------------- + +ym2608::ym2608(ymfm_interface &intf) : + m_fidelity(OPN_FIDELITY_MAX), + m_address(0), + m_irq_enable(0x1f), + m_flag_control(0x1c), + m_fm(intf), + m_ssg(intf), + m_ssg_resampler(m_ssg), + m_adpcm_a(intf, 0), + m_adpcm_b(intf) +{ + m_last_fm.clear(); + update_prescale(m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ym2608::reset() +{ + // reset the engines + m_fm.reset(); + m_ssg.reset(); + m_adpcm_a.reset(); + m_adpcm_b.reset(); + + // configure ADPCM percussion sounds; these are present in an embedded ROM + m_adpcm_a.set_start_end(0, 0x0000, 0x01bf); // bass drum + m_adpcm_a.set_start_end(1, 0x01c0, 0x043f); // snare drum + m_adpcm_a.set_start_end(2, 0x0440, 0x1b7f); // top cymbal + m_adpcm_a.set_start_end(3, 0x1b80, 0x1cff); // high hat + m_adpcm_a.set_start_end(4, 0x1d00, 0x1f7f); // tom tom + m_adpcm_a.set_start_end(5, 0x1f80, 0x1fff); // rim shot + + // initialize our special interrupt states, then read the upper status + // register, which updates the IRQs + m_irq_enable = 0x1f; + m_flag_control = 0x1c; + read_status_hi(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ym2608::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + state.save_restore(m_irq_enable); + state.save_restore(m_flag_control); + state.save_restore(m_last_fm.data); + + m_fm.save_restore(state); + m_ssg.save_restore(state); + m_ssg_resampler.save_restore(state); + m_adpcm_a.save_restore(state); + m_adpcm_b.save_restore(state); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ym2608::read_status() +{ + uint8_t result = m_fm.status() & (fm_engine::STATUS_TIMERA | fm_engine::STATUS_TIMERB); + if (m_fm.intf().ymfm_is_busy()) + result |= fm_engine::STATUS_BUSY; + return result; +} + + +//------------------------------------------------- +// read_data - read the data register +//------------------------------------------------- + +uint8_t ym2608::read_data() +{ + uint8_t result = 0; + if (m_address < 0x10) + { + // 00-0F: Read from SSG + result = m_ssg.read(m_address & 0x0f); + } + else if (m_address == 0xff) + { + // FF: ID code + result = 1; + } + return result; +} + + +//------------------------------------------------- +// read_status_hi - read the extended status +// register +//------------------------------------------------- + +uint8_t ym2608::read_status_hi() +{ + // fetch regular status + uint8_t status = m_fm.status() & ~(STATUS_ADPCM_B_EOS | STATUS_ADPCM_B_BRDY | STATUS_ADPCM_B_PLAYING); + + // fetch ADPCM-B status, and merge in the bits + uint8_t adpcm_status = m_adpcm_b.status(); + if ((adpcm_status & adpcm_b_channel::STATUS_EOS) != 0) + status |= STATUS_ADPCM_B_EOS; + if ((adpcm_status & adpcm_b_channel::STATUS_BRDY) != 0) + status |= STATUS_ADPCM_B_BRDY; + if ((adpcm_status & adpcm_b_channel::STATUS_PLAYING) != 0) + status |= STATUS_ADPCM_B_PLAYING; + + // turn off any bits that have been requested to be masked + status &= ~(m_flag_control & 0x1f); + + // update the status so that IRQs are propagated + m_fm.set_reset_status(status, ~status); + + // merge in the busy flag + if (m_fm.intf().ymfm_is_busy()) + status |= fm_engine::STATUS_BUSY; + return status; +} + + +//------------------------------------------------- +// read_data_hi - read the upper data register +//------------------------------------------------- + +uint8_t ym2608::read_data_hi() +{ + uint8_t result = 0; + if ((m_address & 0xff) < 0x10) + { + // 00-0F: Read from ADPCM-B + result = m_adpcm_b.read(m_address & 0x0f); + } + return result; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ym2608::read(uint32_t offset) +{ + uint8_t result = 0; + switch (offset & 3) + { + case 0: // status port, YM2203 compatible + result = read_status(); + break; + + case 1: // data port (only SSG) + result = read_data(); + break; + + case 2: // status port, extended + result = read_status_hi(); + break; + + case 3: // ADPCM-B data + result = read_data_hi(); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ym2608::write_address(uint8_t data) +{ + // just set the address + m_address = data; + + // special case: update the prescale + if (m_address >= 0x2d && m_address <= 0x2f) + { + // 2D-2F: prescaler select + if (m_address == 0x2d) + update_prescale(6); + else if (m_address == 0x2e && m_fm.clock_prescale() == 6) + update_prescale(3); + else if (m_address == 0x2f) + update_prescale(2); + } +} + + +//------------------------------------------------- +// write - handle a write to the data register +//------------------------------------------------- + +void ym2608::write_data(uint8_t data) +{ + // ignore if paired with upper address + if (bitfield(m_address, 8)) + return; + + if (m_address < 0x10) + { + // 00-0F: write to SSG + m_ssg.write(m_address & 0x0f, data); + } + else if (m_address < 0x20) + { + // 10-1F: write to ADPCM-A + m_adpcm_a.write(m_address & 0x0f, data); + } + else if (m_address == 0x29) + { + // 29: special IRQ mask register + m_irq_enable = data; + m_fm.set_irq_mask(m_irq_enable & ~m_flag_control & 0x1f); + } + else + { + // 20-28, 2A-FF: write to FM + m_fm.write(m_address, data); + } + + // mark busy for a bit + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// write_address_hi - handle a write to the upper +// address register +//------------------------------------------------- + +void ym2608::write_address_hi(uint8_t data) +{ + // just set the address + m_address = 0x100 | data; +} + + +//------------------------------------------------- +// write_data_hi - handle a write to the upper +// data register +//------------------------------------------------- + +void ym2608::write_data_hi(uint8_t data) +{ + // ignore if paired with upper address + if (!bitfield(m_address, 8)) + return; + + if (m_address < 0x110) + { + // 100-10F: write to ADPCM-B + m_adpcm_b.write(m_address & 0x0f, data); + } + else if (m_address == 0x110) + { + // 110: IRQ flag control + if (bitfield(data, 7)) + m_fm.set_reset_status(0, 0xff); + else + { + m_flag_control = data; + m_fm.set_irq_mask(m_irq_enable & ~m_flag_control & 0x1f); + } + } + else + { + // 111-1FF: write to FM + m_fm.write(m_address, data); + } + + // mark busy for a bit + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym2608::write(uint32_t offset, uint8_t data) +{ + switch (offset & 3) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + + case 2: // upper address port + write_address_hi(data); + break; + + case 3: // upper data port + write_data_hi(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void ym2608::generate(output_data *output, uint32_t numsamples) +{ + // FM output is just repeated the prescale number of times; note that + // 0 is a special 1.5 case + if (m_fm_samples_per_output != 0) + { + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + if ((m_ssg_resampler.sampindex() + samp) % m_fm_samples_per_output == 0) + clock_fm_and_adpcm(); + output->data[0] = m_last_fm.data[0]; + output->data[1] = m_last_fm.data[1]; + } + } + else + { + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + uint32_t step = (m_ssg_resampler.sampindex() + samp) % 3; + if (step == 0) + clock_fm_and_adpcm(); + output->data[0] = m_last_fm.data[0]; + output->data[1] = m_last_fm.data[1]; + if (step == 1) + { + clock_fm_and_adpcm(); + output->data[0] = (output->data[0] + m_last_fm.data[0]) / 2; + output->data[1] = (output->data[1] + m_last_fm.data[1]) / 2; + } + } + } + + // resample the SSG as configured + m_ssg_resampler.resample(output - numsamples, numsamples); +} + + +//------------------------------------------------- +// update_prescale - update the prescale value, +// recomputing derived values +//------------------------------------------------- + +void ym2608::update_prescale(uint8_t prescale) +{ + // tell the FM engine + m_fm.set_clock_prescale(prescale); + m_ssg.prescale_changed(); + + // Fidelity: ---- minimum ---- ---- medium ----- ---- maximum----- + // rate = clock/48 rate = clock/24 rate = clock/8 + // Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate + // 6 3:1 2:3 6:1 4:3 18:1 4:1 + // 3 1.5:1 1:3 3:1 2:3 9:1 2:1 + // 2 1:1 1:6 2:1 1:3 6:1 1:1 + + // compute the number of FM samples per output sample, and select the + // resampler function + if (m_fidelity == OPN_FIDELITY_MIN) + { + switch (prescale) + { + default: + case 6: m_fm_samples_per_output = 3; m_ssg_resampler.configure(2, 3); break; + case 3: m_fm_samples_per_output = 0; m_ssg_resampler.configure(1, 3); break; + case 2: m_fm_samples_per_output = 1; m_ssg_resampler.configure(1, 6); break; + } + } + else if (m_fidelity == OPN_FIDELITY_MED) + { + switch (prescale) + { + default: + case 6: m_fm_samples_per_output = 6; m_ssg_resampler.configure(4, 3); break; + case 3: m_fm_samples_per_output = 3; m_ssg_resampler.configure(2, 3); break; + case 2: m_fm_samples_per_output = 2; m_ssg_resampler.configure(1, 3); break; + } + } + else + { + switch (prescale) + { + default: + case 6: m_fm_samples_per_output = 18; m_ssg_resampler.configure(4, 1); break; + case 3: m_fm_samples_per_output = 9; m_ssg_resampler.configure(2, 1); break; + case 2: m_fm_samples_per_output = 6; m_ssg_resampler.configure(1, 1); break; + } + } + + // if overriding the SSG, override the configuration with the nop + // resampler to at least keep the sample index moving forward + if (m_ssg.overridden()) + m_ssg_resampler.configure(0, 0); +} + + +//------------------------------------------------- +// clock_fm_and_adpcm - clock FM and ADPCM state +//------------------------------------------------- + +void ym2608::clock_fm_and_adpcm() +{ + // top bit of the IRQ enable flags controls 3-channel vs 6-channel mode + uint32_t fmmask = bitfield(m_irq_enable, 7) ? 0x3f : 0x07; + + // clock the system + uint32_t env_counter = m_fm.clock(fm_engine::ALL_CHANNELS); + + // clock the ADPCM-A engine on every envelope cycle + // (channels 4 and 5 clock every 2 envelope clocks) + if (bitfield(env_counter, 0, 2) == 0) + m_adpcm_a.clock(bitfield(env_counter, 2) ? 0x0f : 0x3f); + + // clock the ADPCM-B engine every cycle + m_adpcm_b.clock(); + + // update the FM content; OPNA is 13-bit with no intermediate clipping + m_fm.output(m_last_fm.clear(), 1, 32767, fmmask); + + // mix in the ADPCM and clamp + m_adpcm_a.output(m_last_fm, 0x3f); + m_adpcm_b.output(m_last_fm, 1); + m_last_fm.clamp16(); +} + + +//********************************************************* +// YMF288 +//********************************************************* + +// YMF288 is a YM2608 with the following changes: +// * ADPCM-B part removed +// * prescaler removed (fixed at 6) +// * CSM removed +// * Low power mode added +// * SSG tone frequency is altered in some way? (explicitly DC for Tp 0-7, also double volume in some cases) +// * I/O ports removed +// * Shorter busy times +// * All registers can be read + +//------------------------------------------------- +// ymf288 - constructor +//------------------------------------------------- + +ymf288::ymf288(ymfm_interface &intf) : + m_fidelity(OPN_FIDELITY_MAX), + m_address(0), + m_irq_enable(0x03), + m_flag_control(0x03), + m_fm(intf), + m_ssg(intf), + m_ssg_resampler(m_ssg), + m_adpcm_a(intf, 0) +{ + m_last_fm.clear(); + update_prescale(); +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ymf288::reset() +{ + // reset the engines + m_fm.reset(); + m_ssg.reset(); + m_adpcm_a.reset(); + + // configure ADPCM percussion sounds; these are present in an embedded ROM + m_adpcm_a.set_start_end(0, 0x0000, 0x01bf); // bass drum + m_adpcm_a.set_start_end(1, 0x01c0, 0x043f); // snare drum + m_adpcm_a.set_start_end(2, 0x0440, 0x1b7f); // top cymbal + m_adpcm_a.set_start_end(3, 0x1b80, 0x1cff); // high hat + m_adpcm_a.set_start_end(4, 0x1d00, 0x1f7f); // tom tom + m_adpcm_a.set_start_end(5, 0x1f80, 0x1fff); // rim shot + + // initialize our special interrupt states, then read the upper status + // register, which updates the IRQs + m_irq_enable = 0x03; + m_flag_control = 0x00; + read_status_hi(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ymf288::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + state.save_restore(m_irq_enable); + state.save_restore(m_flag_control); + state.save_restore(m_last_fm.data); + + m_fm.save_restore(state); + m_ssg.save_restore(state); + m_ssg_resampler.save_restore(state); + m_adpcm_a.save_restore(state); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ymf288::read_status() +{ + uint8_t result = m_fm.status() & (fm_engine::STATUS_TIMERA | fm_engine::STATUS_TIMERB); + if (m_fm.intf().ymfm_is_busy()) + result |= fm_engine::STATUS_BUSY; + return result; +} + + +//------------------------------------------------- +// read_data - read the data register +//------------------------------------------------- + +uint8_t ymf288::read_data() +{ + uint8_t result = 0; + if (m_address < 0x0e) + { + // 00-0D: Read from SSG + result = m_ssg.read(m_address & 0x0f); + } + else if (m_address < 0x10) + { + // 0E-0F: I/O ports not supported + result = 0xff; + } + else if (m_address == 0xff) + { + // FF: ID code + result = 2; + } + else if (ymf288_mode()) + { + // registers are readable in YMF288 mode + result = m_fm.regs().read(m_address); + } + return result; +} + + +//------------------------------------------------- +// read_status_hi - read the extended status +// register +//------------------------------------------------- + +uint8_t ymf288::read_status_hi() +{ + // fetch regular status + uint8_t status = m_fm.status() & (fm_engine::STATUS_TIMERA | fm_engine::STATUS_TIMERB); + + // turn off any bits that have been requested to be masked + status &= ~(m_flag_control & 0x03); + + // update the status so that IRQs are propagated + m_fm.set_reset_status(status, ~status); + + // merge in the busy flag + if (m_fm.intf().ymfm_is_busy()) + status |= fm_engine::STATUS_BUSY; + return status; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ymf288::read(uint32_t offset) +{ + uint8_t result = 0; + switch (offset & 3) + { + case 0: // status port, YM2203 compatible + result = read_status(); + break; + + case 1: // data port + result = read_data(); + break; + + case 2: // status port, extended + result = read_status_hi(); + break; + + case 3: // unmapped + debug::log_unexpected_read_write("Unexpected read from YMF288 offset %d\n", offset & 3); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ymf288::write_address(uint8_t data) +{ + // just set the address + m_address = data; + + // in YMF288 mode, busy is signaled after address writes too + if (ymf288_mode()) + m_fm.intf().ymfm_set_busy_end(16); +} + + +//------------------------------------------------- +// write - handle a write to the data register +//------------------------------------------------- + +void ymf288::write_data(uint8_t data) +{ + // ignore if paired with upper address + if (bitfield(m_address, 8)) + return; + + // wait times are shorter in YMF288 mode + int busy_cycles = ymf288_mode() ? 16 : 32 * m_fm.clock_prescale(); + if (m_address < 0x0e) + { + // 00-0D: write to SSG + m_ssg.write(m_address & 0x0f, data); + } + else if (m_address < 0x10) + { + // 0E-0F: I/O ports not supported + } + else if (m_address < 0x20) + { + // 10-1F: write to ADPCM-A + m_adpcm_a.write(m_address & 0x0f, data); + busy_cycles = 32 * m_fm.clock_prescale(); + } + else if (m_address == 0x27) + { + // 27: mode register; CSM isn't supported so disable it + data &= 0x7f; + m_fm.write(m_address, data); + } + else if (m_address == 0x29) + { + // 29: special IRQ mask register + m_irq_enable = data; + m_fm.set_irq_mask(m_irq_enable & ~m_flag_control & 0x03); + } + else + { + // 20-27, 2A-FF: write to FM + m_fm.write(m_address, data); + } + + // mark busy for a bit + m_fm.intf().ymfm_set_busy_end(busy_cycles); +} + + +//------------------------------------------------- +// write_address_hi - handle a write to the upper +// address register +//------------------------------------------------- + +void ymf288::write_address_hi(uint8_t data) +{ + // just set the address + m_address = 0x100 | data; + + // in YMF288 mode, busy is signaled after address writes too + if (ymf288_mode()) + m_fm.intf().ymfm_set_busy_end(16); +} + + +//------------------------------------------------- +// write_data_hi - handle a write to the upper +// data register +//------------------------------------------------- + +void ymf288::write_data_hi(uint8_t data) +{ + // ignore if paired with upper address + if (!bitfield(m_address, 8)) + return; + + // wait times are shorter in YMF288 mode + int busy_cycles = ymf288_mode() ? 16 : 32 * m_fm.clock_prescale(); + if (m_address == 0x110) + { + // 110: IRQ flag control + if (bitfield(data, 7)) + m_fm.set_reset_status(0, 0xff); + else + { + m_flag_control = data; + m_fm.set_irq_mask(m_irq_enable & ~m_flag_control & 0x03); + } + } + else + { + // 100-10F,111-1FF: write to FM + m_fm.write(m_address, data); + } + + // mark busy for a bit + m_fm.intf().ymfm_set_busy_end(busy_cycles); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ymf288::write(uint32_t offset, uint8_t data) +{ + switch (offset & 3) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + + case 2: // upper address port + write_address_hi(data); + break; + + case 3: // upper data port + write_data_hi(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void ymf288::generate(output_data *output, uint32_t numsamples) +{ + // FM output is just repeated the prescale number of times; note that + // 0 is a special 1.5 case + if (m_fm_samples_per_output != 0) + { + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + if ((m_ssg_resampler.sampindex() + samp) % m_fm_samples_per_output == 0) + clock_fm_and_adpcm(); + output->data[0] = m_last_fm.data[0]; + output->data[1] = m_last_fm.data[1]; + } + } + else + { + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + uint32_t step = (m_ssg_resampler.sampindex() + samp) % 3; + if (step == 0) + clock_fm_and_adpcm(); + output->data[0] = m_last_fm.data[0]; + output->data[1] = m_last_fm.data[1]; + if (step == 1) + { + clock_fm_and_adpcm(); + output->data[0] = (output->data[0] + m_last_fm.data[0]) / 2; + output->data[1] = (output->data[1] + m_last_fm.data[1]) / 2; + } + } + } + + // resample the SSG as configured + m_ssg_resampler.resample(output - numsamples, numsamples); +} + + +//------------------------------------------------- +// update_prescale - update the prescale value, +// recomputing derived values +//------------------------------------------------- + +void ymf288::update_prescale() +{ + // Fidelity: ---- minimum ---- ---- medium ----- ---- maximum----- + // rate = clock/144 rate = clock/144 rate = clock/16 + // Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate + // 6 1:1 2:9 1:1 2:9 9:1 2:1 + + // compute the number of FM samples per output sample, and select the + // resampler function + if (m_fidelity == OPN_FIDELITY_MIN || m_fidelity == OPN_FIDELITY_MED) + { + m_fm_samples_per_output = 1; + m_ssg_resampler.configure(2, 9); + } + else + { + m_fm_samples_per_output = 9; + m_ssg_resampler.configure(2, 1); + } + + // if overriding the SSG, override the configuration with the nop + // resampler to at least keep the sample index moving forward + if (m_ssg.overridden()) + m_ssg_resampler.configure(0, 0); +} + + +//------------------------------------------------- +// clock_fm_and_adpcm - clock FM and ADPCM state +//------------------------------------------------- + +void ymf288::clock_fm_and_adpcm() +{ + // top bit of the IRQ enable flags controls 3-channel vs 6-channel mode + uint32_t fmmask = bitfield(m_irq_enable, 7) ? 0x3f : 0x07; + + // clock the system + uint32_t env_counter = m_fm.clock(fm_engine::ALL_CHANNELS); + + // clock the ADPCM-A engine on every envelope cycle + // (channels 4 and 5 clock every 2 envelope clocks) + if (bitfield(env_counter, 0, 2) == 0) + m_adpcm_a.clock(bitfield(env_counter, 2) ? 0x0f : 0x3f); + + // update the FM content; OPNA is 13-bit with no intermediate clipping + m_fm.output(m_last_fm.clear(), 1, 32767, fmmask); + + // mix in the ADPCM + m_adpcm_a.output(m_last_fm, 0x3f); +} + + + +//********************************************************* +// YM2610 +//********************************************************* + +//------------------------------------------------- +// ym2610 - constructor +//------------------------------------------------- + +ym2610::ym2610(ymfm_interface &intf, uint8_t channel_mask) : + m_fidelity(OPN_FIDELITY_MAX), + m_address(0), + m_fm_mask(channel_mask), + m_eos_status(0x00), + m_flag_mask(EOS_FLAGS_MASK), + m_fm(intf), + m_ssg(intf), + m_ssg_resampler(m_ssg), + m_adpcm_a(intf, 8), + m_adpcm_b(intf, 8) +{ + update_prescale(); +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ym2610::reset() +{ + // reset the engines + m_fm.reset(); + m_ssg.reset(); + m_adpcm_a.reset(); + m_adpcm_b.reset(); + + // initialize our special interrupt states + m_eos_status = 0x00; + m_flag_mask = EOS_FLAGS_MASK; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ym2610::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + state.save_restore(m_eos_status); + state.save_restore(m_flag_mask); + + m_fm.save_restore(state); + m_ssg.save_restore(state); + m_ssg_resampler.save_restore(state); + m_adpcm_a.save_restore(state); + m_adpcm_b.save_restore(state); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ym2610::read_status() +{ + uint8_t result = m_fm.status() & (fm_engine::STATUS_TIMERA | fm_engine::STATUS_TIMERB); + if (m_fm.intf().ymfm_is_busy()) + result |= fm_engine::STATUS_BUSY; + return result; +} + + +//------------------------------------------------- +// read_data - read the data register +//------------------------------------------------- + +uint8_t ym2610::read_data() +{ + uint8_t result = 0; + if (m_address < 0x0e) + { + // 00-0D: Read from SSG + result = m_ssg.read(m_address & 0x0f); + } + else if (m_address < 0x10) + { + // 0E-0F: I/O ports not supported + result = 0xff; + } + else if (m_address == 0xff) + { + // FF: ID code + result = 1; + } + return result; +} + + +//------------------------------------------------- +// read_status_hi - read the extended status +// register +//------------------------------------------------- + +uint8_t ym2610::read_status_hi() +{ + return m_eos_status & m_flag_mask; +} + + +//------------------------------------------------- +// read_data_hi - read the upper data register +//------------------------------------------------- + +uint8_t ym2610::read_data_hi() +{ + uint8_t result = 0; + return result; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ym2610::read(uint32_t offset) +{ + uint8_t result = 0; + switch (offset & 3) + { + case 0: // status port, YM2203 compatible + result = read_status(); + break; + + case 1: // data port (only SSG) + result = read_data(); + break; + + case 2: // status port, extended + result = read_status_hi(); + break; + + case 3: // ADPCM-B data + result = read_data_hi(); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ym2610::write_address(uint8_t data) +{ + // just set the address + m_address = data; +} + + +//------------------------------------------------- +// write - handle a write to the data register +//------------------------------------------------- + +void ym2610::write_data(uint8_t data) +{ + // ignore if paired with upper address + if (bitfield(m_address, 8)) + return; + + if (m_address < 0x0e) + { + // 00-0D: write to SSG + m_ssg.write(m_address & 0x0f, data); + } + else if (m_address < 0x10) + { + // 0E-0F: I/O ports not supported + } + else if (m_address < 0x1c) + { + // 10-1B: write to ADPCM-B + // YM2610 effectively forces external mode on, and disables recording + if (m_address == 0x10) + data = (data | 0x20) & ~0x40; + m_adpcm_b.write(m_address & 0x0f, data); + } + else if (m_address == 0x1c) + { + // 1C: EOS flag reset + m_flag_mask = ~data & EOS_FLAGS_MASK; + m_eos_status &= ~(data & EOS_FLAGS_MASK); + } + else + { + // 1D-FF: write to FM + m_fm.write(m_address, data); + } + + // mark busy for a bit + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// write_address_hi - handle a write to the upper +// address register +//------------------------------------------------- + +void ym2610::write_address_hi(uint8_t data) +{ + // just set the address + m_address = 0x100 | data; +} + + +//------------------------------------------------- +// write_data_hi - handle a write to the upper +// data register +//------------------------------------------------- + +void ym2610::write_data_hi(uint8_t data) +{ + // ignore if paired with upper address + if (!bitfield(m_address, 8)) + return; + + if (m_address < 0x130) + { + // 100-12F: write to ADPCM-A + m_adpcm_a.write(m_address & 0x3f, data); + } + else + { + // 130-1FF: write to FM + m_fm.write(m_address, data); + } + + // mark busy for a bit + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym2610::write(uint32_t offset, uint8_t data) +{ + switch (offset & 3) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + + case 2: // upper address port + write_address_hi(data); + break; + + case 3: // upper data port + write_data_hi(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void ym2610::generate(output_data *output, uint32_t numsamples) +{ + // FM output is just repeated the prescale number of times + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + if ((m_ssg_resampler.sampindex() + samp) % m_fm_samples_per_output == 0) + clock_fm_and_adpcm(); + output->data[0] = m_last_fm.data[0]; + output->data[1] = m_last_fm.data[1]; + } + + // resample the SSG as configured + m_ssg_resampler.resample(output - numsamples, numsamples); +} + + +//------------------------------------------------- +// update_prescale - update the prescale value, +// recomputing derived values +//------------------------------------------------- + +void ym2610::update_prescale() +{ + // Fidelity: ---- minimum ---- ---- medium ----- ---- maximum----- + // rate = clock/144 rate = clock/144 rate = clock/16 + // Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate + // 6 1:1 2:9 1:1 2:9 9:1 2:1 + + // compute the number of FM samples per output sample, and select the + // resampler function + if (m_fidelity == OPN_FIDELITY_MIN || m_fidelity == OPN_FIDELITY_MED) + { + m_fm_samples_per_output = 1; + m_ssg_resampler.configure(2, 9); + } + else + { + m_fm_samples_per_output = 9; + m_ssg_resampler.configure(2, 1); + } + + // if overriding the SSG, override the configuration with the nop + // resampler to at least keep the sample index moving forward + if (m_ssg.overridden()) + m_ssg_resampler.configure(0, 0); +} + + +//------------------------------------------------- +// clock_fm_and_adpcm - clock FM and ADPCM state +//------------------------------------------------- + +void ym2610::clock_fm_and_adpcm() +{ + // clock the system + uint32_t env_counter = m_fm.clock(m_fm_mask); + + // clock the ADPCM-A engine on every envelope cycle + if (bitfield(env_counter, 0, 2) == 0) + m_eos_status |= m_adpcm_a.clock(0x3f); + + // clock the ADPCM-B engine every cycle + m_adpcm_b.clock(); + + // we track the last ADPCM-B EOS value in bit 6 (which is hidden from callers); + // if it changed since the last sample, update the visible EOS state in bit 7 + uint8_t live_eos = ((m_adpcm_b.status() & adpcm_b_channel::STATUS_EOS) != 0) ? 0x40 : 0x00; + if (((live_eos ^ m_eos_status) & 0x40) != 0) + m_eos_status = (m_eos_status & ~0xc0) | live_eos | (live_eos << 1); + + // update the FM content; OPNB is 13-bit with no intermediate clipping + m_fm.output(m_last_fm.clear(), 1, 32767, m_fm_mask); + + // mix in the ADPCM and clamp + m_adpcm_a.output(m_last_fm, 0x3f); + m_adpcm_b.output(m_last_fm, 1); + m_last_fm.clamp16(); +} + + + +//********************************************************* +// YM2612 +//********************************************************* + +//------------------------------------------------- +// ym2612 - constructor +//------------------------------------------------- + +ym2612::ym2612(ymfm_interface &intf) : + m_address(0), + m_dac_data(0), + m_dac_enable(0), + m_fm(intf) +{ +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ym2612::reset() +{ + // reset the engines + m_fm.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ym2612::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + state.save_restore(m_dac_data); + state.save_restore(m_dac_enable); + m_fm.save_restore(state); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ym2612::read_status() +{ + uint8_t result = m_fm.status(); + if (m_fm.intf().ymfm_is_busy()) + result |= fm_engine::STATUS_BUSY; + return result; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ym2612::read(uint32_t offset) +{ + uint8_t result = 0; + switch (offset & 3) + { + case 0: // status port, YM2203 compatible + result = read_status(); + break; + + case 1: // data port (unused) + case 2: // status port, extended + case 3: // data port (unused) + debug::log_unexpected_read_write("Unexpected read from YM2612 offset %d\n", offset & 3); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ym2612::write_address(uint8_t data) +{ + // just set the address + m_address = data; +} + + +//------------------------------------------------- +// write_data - handle a write to the data +// register +//------------------------------------------------- + +void ym2612::write_data(uint8_t data) +{ + // ignore if paired with upper address + if (bitfield(m_address, 8)) + return; + + if (m_address == 0x2a) + { + // 2A: DAC data (most significant 8 bits) + m_dac_data = (m_dac_data & ~0x1fe) | ((data ^ 0x80) << 1); + } + else if (m_address == 0x2b) + { + // 2B: DAC enable (bit 7) + m_dac_enable = bitfield(data, 7); + } + else if (m_address == 0x2c) + { + // 2C: test/low DAC bit + m_dac_data = (m_dac_data & ~1) | bitfield(data, 3); + } + else + { + // 00-29, 2D-FF: write to FM + m_fm.write(m_address, data); + } + + // mark busy for a bit + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// write_address_hi - handle a write to the upper +// address register +//------------------------------------------------- + +void ym2612::write_address_hi(uint8_t data) +{ + // just set the address + m_address = 0x100 | data; +} + + +//------------------------------------------------- +// write_data_hi - handle a write to the upper +// data register +//------------------------------------------------- + +void ym2612::write_data_hi(uint8_t data) +{ + // ignore if paired with upper address + if (!bitfield(m_address, 8)) + return; + + // 100-1FF: write to FM + m_fm.write(m_address, data); + + // mark busy for a bit + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym2612::write(uint32_t offset, uint8_t data) +{ + switch (offset & 3) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + + case 2: // upper address port + write_address_hi(data); + break; + + case 3: // upper data port + write_data_hi(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void ym2612::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // sum individual channels to apply DAC discontinuity on each + output->clear(); + output_data temp; + + // first do FM-only channels; OPN2 is 9-bit with intermediate clipping + int const last_fm_channel = m_dac_enable ? 5 : 6; + for (int chan = 0; chan < last_fm_channel; chan++) + { + m_fm.output(temp.clear(), 5, 256, 1 << chan); + output->data[0] += dac_discontinuity(temp.data[0]); + output->data[1] += dac_discontinuity(temp.data[1]); + } + + // add in DAC + if (m_dac_enable) + { + // DAC enabled: start with DAC value then add the first 5 channels only + int32_t dacval = dac_discontinuity(int16_t(m_dac_data << 7) >> 7); + output->data[0] += m_fm.regs().ch_output_0(0x102) ? dacval : dac_discontinuity(0); + output->data[1] += m_fm.regs().ch_output_1(0x102) ? dacval : dac_discontinuity(0); + } + + // output is technically multiplexed rather than mixed, but that requires + // a better sound mixer than we usually have, so just average over the six + // channels; also apply a 64/65 factor to account for the discontinuity + // adjustment above + output->data[0] = (output->data[0] * 128) * 64 / (6 * 65); + output->data[1] = (output->data[1] * 128) * 64 / (6 * 65); + } +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void ym3438::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // first do FM-only channels; OPN2C is 9-bit with intermediate clipping + if (!m_dac_enable) + { + // DAC disabled: all 6 channels sum together + m_fm.output(output->clear(), 5, 256, fm_engine::ALL_CHANNELS); + } + else + { + // DAC enabled: start with DAC value then add the first 5 channels only + int32_t dacval = int16_t(m_dac_data << 7) >> 7; + output->data[0] = m_fm.regs().ch_output_0(0x102) ? dacval : 0; + output->data[1] = m_fm.regs().ch_output_1(0x102) ? dacval : 0; + m_fm.output(*output, 5, 256, fm_engine::ALL_CHANNELS ^ (1 << 5)); + } + + // YM3438 doesn't have the same DAC discontinuity, though its output is + // multiplexed like the YM2612 + output->data[0] = (output->data[0] * 128) / 6; + output->data[1] = (output->data[1] * 128) / 6; + } +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void ymf276::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // first do FM-only channels; OPN2L is 14-bit with intermediate clipping + if (!m_dac_enable) + { + // DAC disabled: all 6 channels sum together + m_fm.output(output->clear(), 0, 8191, fm_engine::ALL_CHANNELS); + } + else + { + // DAC enabled: start with DAC value then add the first 5 channels only + int32_t dacval = int16_t(m_dac_data << 7) >> 7; + output->data[0] = m_fm.regs().ch_output_0(0x102) ? dacval : 0; + output->data[1] = m_fm.regs().ch_output_1(0x102) ? dacval : 0; + m_fm.output(*output, 0, 8191, fm_engine::ALL_CHANNELS ^ (1 << 5)); + } + + // YMF276 is properly mixed; it shifts down 1 bit before clamping + output->data[0] = clamp(output->data[0] >> 1, -32768, 32767); + output->data[1] = clamp(output->data[1] >> 1, -32768, 32767); + } +} + +} diff --git a/src/sound/ymfm/ymfm_opn.h b/src/sound/ymfm/ymfm_opn.h new file mode 100644 index 000000000..bab68ed93 --- /dev/null +++ b/src/sound/ymfm/ymfm_opn.h @@ -0,0 +1,802 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_OPN_H +#define YMFM_OPN_H + +#pragma once + +#include "ymfm.h" +#include "ymfm_adpcm.h" +#include "ymfm_fm.h" +#include "ymfm_ssg.h" + +namespace ymfm +{ + +//********************************************************* +// REGISTER CLASSES +//********************************************************* + +// ======================> opn_registers_base + +// +// OPN register map: +// +// System-wide registers: +// 21 xxxxxxxx Test register +// 22 ----x--- LFO enable [OPNA+ only] +// -----xxx LFO rate [OPNA+ only] +// 24 xxxxxxxx Timer A value (upper 8 bits) +// 25 ------xx Timer A value (lower 2 bits) +// 26 xxxxxxxx Timer B value +// 27 xx------ CSM/Multi-frequency mode for channel #2 +// --x----- Reset timer B +// ---x---- Reset timer A +// ----x--- Enable timer B +// -----x-- Enable timer A +// ------x- Load timer B +// -------x Load timer A +// 28 x------- Key on/off operator 4 +// -x------ Key on/off operator 3 +// --x----- Key on/off operator 2 +// ---x---- Key on/off operator 1 +// ------xx Channel select +// +// Per-channel registers (channel in address bits 0-1) +// Note that all these apply to address+100 as well on OPNA+ +// A0-A3 xxxxxxxx Frequency number lower 8 bits +// A4-A7 --xxx--- Block (0-7) +// -----xxx Frequency number upper 3 bits +// B0-B3 --xxx--- Feedback level for operator 1 (0-7) +// -----xxx Operator connection algorithm (0-7) +// B4-B7 x------- Pan left [OPNA] +// -x------ Pan right [OPNA] +// --xx---- LFO AM shift (0-3) [OPNA+ only] +// -----xxx LFO PM depth (0-7) [OPNA+ only] +// +// Per-operator registers (channel in address bits 0-1, operator in bits 2-3) +// Note that all these apply to address+100 as well on OPNA+ +// 30-3F -xxx---- Detune value (0-7) +// ----xxxx Multiple value (0-15) +// 40-4F -xxxxxxx Total level (0-127) +// 50-5F xx------ Key scale rate (0-3) +// ---xxxxx Attack rate (0-31) +// 60-6F x------- LFO AM enable [OPNA] +// ---xxxxx Decay rate (0-31) +// 70-7F ---xxxxx Sustain rate (0-31) +// 80-8F xxxx---- Sustain level (0-15) +// ----xxxx Release rate (0-15) +// 90-9F ----x--- SSG-EG enable +// -----xxx SSG-EG envelope (0-7) +// +// Special multi-frequency registers (channel implicitly #2; operator in address bits 0-1) +// A8-AB xxxxxxxx Frequency number lower 8 bits +// AC-AF --xxx--- Block (0-7) +// -----xxx Frequency number upper 3 bits +// +// Internal (fake) registers: +// B8-BB --xxxxxx Latched frequency number upper bits (from A4-A7) +// BC-BF --xxxxxx Latched frequency number upper bits (from AC-AF) +// + +template +class opn_registers_base : public fm_registers_base +{ +public: + // constants + static constexpr uint32_t OUTPUTS = IsOpnA ? 2 : 1; + static constexpr uint32_t CHANNELS = IsOpnA ? 6 : 3; + static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1; + static constexpr uint32_t OPERATORS = CHANNELS * 4; + static constexpr uint32_t WAVEFORMS = 1; + static constexpr uint32_t REGISTERS = IsOpnA ? 0x200 : 0x100; + static constexpr uint32_t REG_MODE = 0x27; + static constexpr uint32_t DEFAULT_PRESCALE = 6; + static constexpr uint32_t EG_CLOCK_DIVIDER = 3; + static constexpr bool EG_HAS_SSG = true; + static constexpr bool MODULATOR_DELAY = false; + static constexpr uint32_t CSM_TRIGGER_MASK = 1 << 2; + static constexpr uint8_t STATUS_TIMERA = 0x01; + static constexpr uint8_t STATUS_TIMERB = 0x02; + static constexpr uint8_t STATUS_BUSY = 0x80; + static constexpr uint8_t STATUS_IRQ = 0; + + // constructor + opn_registers_base(); + + // reset to initial state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // map channel number to register offset + static constexpr uint32_t channel_offset(uint32_t chnum) + { + assert(chnum < CHANNELS); + if (!IsOpnA) + return chnum; + else + return (chnum % 3) + 0x100 * (chnum / 3); + } + + // map operator number to register offset + static constexpr uint32_t operator_offset(uint32_t opnum) + { + assert(opnum < OPERATORS); + if (!IsOpnA) + return opnum + opnum / 3; + else + return (opnum % 12) + ((opnum % 12) / 3) + 0x100 * (opnum / 12); + } + + // return an array of operator indices for each channel + struct operator_mapping { uint32_t chan[CHANNELS]; }; + void operator_map(operator_mapping &dest) const; + + // read a register value + uint8_t read(uint16_t index) const { return m_regdata[index]; } + + // handle writes to the register array + bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask); + + // clock the noise and LFO, if present, returning LFO PM value + int32_t clock_noise_and_lfo(); + + // reset the LFO + void reset_lfo() { m_lfo_counter = 0; } + + // return the AM offset from LFO for the given channel + uint32_t lfo_am_offset(uint32_t choffs) const; + + // return LFO/noise states + uint32_t noise_state() const { return 0; } + + // caching helpers + void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache); + + // compute the phase step, given a PM value + uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm); + + // log a key-on event + std::string log_keyon(uint32_t choffs, uint32_t opoffs); + + // system-wide registers + uint32_t test() const { return byte(0x21, 0, 8); } + uint32_t lfo_enable() const { return IsOpnA ? byte(0x22, 3, 1) : 0; } + uint32_t lfo_rate() const { return IsOpnA ? byte(0x22, 0, 3) : 0; } + uint32_t timer_a_value() const { return word(0x24, 0, 8, 0x25, 0, 2); } + uint32_t timer_b_value() const { return byte(0x26, 0, 8); } + uint32_t csm() const { return (byte(0x27, 6, 2) == 2); } + uint32_t multi_freq() const { return (byte(0x27, 6, 2) != 0); } + uint32_t reset_timer_b() const { return byte(0x27, 5, 1); } + uint32_t reset_timer_a() const { return byte(0x27, 4, 1); } + uint32_t enable_timer_b() const { return byte(0x27, 3, 1); } + uint32_t enable_timer_a() const { return byte(0x27, 2, 1); } + uint32_t load_timer_b() const { return byte(0x27, 1, 1); } + uint32_t load_timer_a() const { return byte(0x27, 0, 1); } + uint32_t multi_block_freq(uint32_t num) const { return word(0xac, 0, 6, 0xa8, 0, 8, num); } + + // per-channel registers + uint32_t ch_block_freq(uint32_t choffs) const { return word(0xa4, 0, 6, 0xa0, 0, 8, choffs); } + uint32_t ch_feedback(uint32_t choffs) const { return byte(0xb0, 3, 3, choffs); } + uint32_t ch_algorithm(uint32_t choffs) const { return byte(0xb0, 0, 3, choffs); } + uint32_t ch_output_any(uint32_t choffs) const { return IsOpnA ? byte(0xb4, 6, 2, choffs) : 1; } + uint32_t ch_output_0(uint32_t choffs) const { return IsOpnA ? byte(0xb4, 7, 1, choffs) : 1; } + uint32_t ch_output_1(uint32_t choffs) const { return IsOpnA ? byte(0xb4, 6, 1, choffs) : 0; } + uint32_t ch_output_2(uint32_t choffs) const { return 0; } + uint32_t ch_output_3(uint32_t choffs) const { return 0; } + uint32_t ch_lfo_am_sens(uint32_t choffs) const { return IsOpnA ? byte(0xb4, 4, 2, choffs) : 0; } + uint32_t ch_lfo_pm_sens(uint32_t choffs) const { return IsOpnA ? byte(0xb4, 0, 3, choffs) : 0; } + + // per-operator registers + uint32_t op_detune(uint32_t opoffs) const { return byte(0x30, 4, 3, opoffs); } + uint32_t op_multiple(uint32_t opoffs) const { return byte(0x30, 0, 4, opoffs); } + uint32_t op_total_level(uint32_t opoffs) const { return byte(0x40, 0, 7, opoffs); } + uint32_t op_ksr(uint32_t opoffs) const { return byte(0x50, 6, 2, opoffs); } + uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x50, 0, 5, opoffs); } + uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0x60, 0, 5, opoffs); } + uint32_t op_lfo_am_enable(uint32_t opoffs) const { return IsOpnA ? byte(0x60, 7, 1, opoffs) : 0; } + uint32_t op_sustain_rate(uint32_t opoffs) const { return byte(0x70, 0, 5, opoffs); } + uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0x80, 4, 4, opoffs); } + uint32_t op_release_rate(uint32_t opoffs) const { return byte(0x80, 0, 4, opoffs); } + uint32_t op_ssg_eg_enable(uint32_t opoffs) const { return byte(0x90, 3, 1, opoffs); } + uint32_t op_ssg_eg_mode(uint32_t opoffs) const { return byte(0x90, 0, 3, opoffs); } + +protected: + // return a bitfield extracted from a byte + uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const + { + return bitfield(m_regdata[offset + extra_offset], start, count); + } + + // return a bitfield extracted from a pair of bytes, MSBs listed first + uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const + { + return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset); + } + + // internal state + uint32_t m_lfo_counter; // LFO counter + uint8_t m_lfo_am; // current LFO AM value + uint8_t m_regdata[REGISTERS]; // register data + uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms +}; + +using opn_registers = opn_registers_base; +using opna_registers = opn_registers_base; + + + +//********************************************************* +// OPN IMPLEMENTATION CLASSES +//********************************************************* + +// A note about prescaling and sample rates. +// +// YM2203, YM2608, and YM2610 contain an onboard SSG (basically, a YM2149). +// In order to properly generate sound at fully fidelity, the output sample +// rate of the YM2149 must be input_clock / 8. This is much higher than the +// FM needs, but in the interest of keeping things simple, the OPN generate +// functions will output at the higher rate and just replicate the last FM +// sample as many times as needed. +// +// To make things even more complicated, the YM2203 and YM2608 allow for +// software-controlled prescaling, which affects the FM and SSG clocks in +// different ways. There are three settings: divide by 6/4 (FM/SSG); divide +// by 3/2; and divide by 2/1. +// +// Thus, the minimum output sample rate needed by each part of the chip +// varies with the prescale as follows: +// +// ---- YM2203 ----- ---- YM2608 ----- ---- YM2610 ----- +// Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate +// 6 /72 /16 /144 /32 /144 /32 +// 3 /36 /8 /72 /16 +// 2 /24 /4 /48 /8 +// +// If we standardized on the fastest SSG rate, we'd end up with the following +// (ratios are output_samples:source_samples): +// +// ---- YM2203 ----- ---- YM2608 ----- ---- YM2610 ----- +// rate = clock/4 rate = clock/8 rate = clock/16 +// Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate +// 6 18:1 4:1 18:1 4:1 9:1 2:1 +// 3 9:1 2:1 9:1 2:1 +// 2 6:1 1:1 6:1 1:1 +// +// However, that's a pretty big performance hit for minimal gain. Going to +// the other extreme, we could standardize on the fastest FM rate, but then +// at least one prescale case (3) requires the FM to be smeared across two +// output samples: +// +// ---- YM2203 ----- ---- YM2608 ----- ---- YM2610 ----- +// rate = clock/24 rate = clock/48 rate = clock/144 +// Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate +// 6 3:1 2:3 3:1 2:3 1:1 2:9 +// 3 1.5:1 1:3 1.5:1 1:3 +// 2 1:1 1:6 1:1 1:6 +// +// Stepping back one factor of 2 addresses that issue: +// +// ---- YM2203 ----- ---- YM2608 ----- ---- YM2610 ----- +// rate = clock/12 rate = clock/24 rate = clock/144 +// Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate +// 6 6:1 4:3 6:1 4:3 1:1 2:9 +// 3 3:1 2:3 3:1 2:3 +// 2 2:1 1:3 2:1 1:3 +// +// This gives us three levels of output fidelity: +// OPN_FIDELITY_MAX -- highest sample rate, using fastest SSG rate +// OPN_FIDELITY_MIN -- lowest sample rate, using fastest FM rate +// OPN_FIDELITY_MED -- medium sample rate such that FM is never smeared +// +// At the maximum clocks for YM2203/YM2608 (4Mhz/8MHz), these rates will +// end up as: +// OPN_FIDELITY_MAX = 1000kHz +// OPN_FIDELITY_MIN = 166kHz +// OPN_FIEDLITY_MED = 333kHz + + +// ======================> opn_fidelity + +enum opn_fidelity : uint8_t +{ + OPN_FIDELITY_MAX, + OPN_FIDELITY_MIN, + OPN_FIDELITY_MED, + + OPN_FIDELITY_DEFAULT = OPN_FIDELITY_MAX +}; + + +// ======================> ssg_resampler + +template +class ssg_resampler +{ +private: + // helper to add the last computed value to the sums, applying the given scale + void add_last(int32_t &sum0, int32_t &sum1, int32_t &sum2, int32_t scale = 1); + + // helper to clock a new value and then add it to the sums, applying the given scale + void clock_and_add(int32_t &sum0, int32_t &sum1, int32_t &sum2, int32_t scale = 1); + + // helper to write the sums to the appropriate outputs, applying the given + // divisor to the final result + void write_to_output(OutputType *output, int32_t sum0, int32_t sum1, int32_t sum2, int32_t divisor = 1); + +public: + // constructor + ssg_resampler(ssg_engine &ssg); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // get the current sample index + uint32_t sampindex() const { return m_sampindex; } + + // configure the ratio + void configure(uint8_t outsamples, uint8_t srcsamples); + + // resample + void resample(OutputType *output, uint32_t numsamples) + { + (this->*m_resampler)(output, numsamples); + } + +private: + // resample SSG output to the target at a rate of 1 SSG sample + // to every n output samples + template + void resample_n_1(OutputType *output, uint32_t numsamples); + + // resample SSG output to the target at a rate of n SSG samples + // to every 1 output sample + template + void resample_1_n(OutputType *output, uint32_t numsamples); + + // resample SSG output to the target at a rate of 9 SSG samples + // to every 2 output samples + void resample_2_9(OutputType *output, uint32_t numsamples); + + // resample SSG output to the target at a rate of 3 SSG samples + // to every 1 output sample + void resample_1_3(OutputType *output, uint32_t numsamples); + + // resample SSG output to the target at a rate of 3 SSG samples + // to every 2 output samples + void resample_2_3(OutputType *output, uint32_t numsamples); + + // resample SSG output to the target at a rate of 3 SSG samples + // to every 4 output samples + void resample_4_3(OutputType *output, uint32_t numsamples); + + // no-op resampler + void resample_nop(OutputType *output, uint32_t numsamples); + + // define a pointer type + using resample_func = void (ssg_resampler::*)(OutputType *output, uint32_t numsamples); + + // internal state + ssg_engine &m_ssg; + uint32_t m_sampindex; + resample_func m_resampler; + ssg_engine::output_data m_last; +}; + + +// ======================> ym2203 + +class ym2203 +{ +public: + using fm_engine = fm_engine_base; + static constexpr uint32_t FM_OUTPUTS = fm_engine::OUTPUTS; + static constexpr uint32_t SSG_OUTPUTS = ssg_engine::OUTPUTS; + static constexpr uint32_t OUTPUTS = FM_OUTPUTS + SSG_OUTPUTS; + using output_data = ymfm_output; + + // constructor + ym2203(ymfm_interface &intf); + + // configuration + void ssg_override(ssg_override &intf) { m_ssg.override(intf); } + void set_fidelity(opn_fidelity fidelity) { m_fidelity = fidelity; update_prescale(m_fm.clock_prescale()); } + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const + { + switch (m_fidelity) + { + case OPN_FIDELITY_MIN: return input_clock / 24; + case OPN_FIDELITY_MED: return input_clock / 12; + default: + case OPN_FIDELITY_MAX: return input_clock / 4; + } + } + uint32_t ssg_effective_clock(uint32_t input_clock) const { uint32_t scale = m_fm.clock_prescale() * 2 / 3; return input_clock * 2 / scale; } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read_data(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal helpers + void update_prescale(uint8_t prescale); + void clock_fm(); + + // internal state + opn_fidelity m_fidelity; // configured fidelity + uint8_t m_address; // address register + uint8_t m_fm_samples_per_output; // how many samples to repeat + fm_engine::output_data m_last_fm; // last FM output + fm_engine m_fm; // core FM engine + ssg_engine m_ssg; // SSG engine + ssg_resampler m_ssg_resampler; // SSG resampler helper +}; + + + +//********************************************************* +// OPNA IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ym2608 + +class ym2608 +{ + static constexpr uint8_t STATUS_ADPCM_B_EOS = 0x04; + static constexpr uint8_t STATUS_ADPCM_B_BRDY = 0x08; + static constexpr uint8_t STATUS_ADPCM_B_ZERO = 0x10; + static constexpr uint8_t STATUS_ADPCM_B_PLAYING = 0x20; + +public: + using fm_engine = fm_engine_base; + static constexpr uint32_t FM_OUTPUTS = fm_engine::OUTPUTS; + static constexpr uint32_t SSG_OUTPUTS = 1; + static constexpr uint32_t OUTPUTS = FM_OUTPUTS + SSG_OUTPUTS; + using output_data = ymfm_output; + + // constructor + ym2608(ymfm_interface &intf); + + // configuration + void ssg_override(ssg_override &intf) { m_ssg.override(intf); } + void set_fidelity(opn_fidelity fidelity) { m_fidelity = fidelity; update_prescale(m_fm.clock_prescale()); } + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const + { + switch (m_fidelity) + { + case OPN_FIDELITY_MIN: return input_clock / 48; + case OPN_FIDELITY_MED: return input_clock / 24; + default: + case OPN_FIDELITY_MAX: return input_clock / 8; + } + } + uint32_t ssg_effective_clock(uint32_t input_clock) const { uint32_t scale = m_fm.clock_prescale() * 2 / 3; return input_clock / scale; } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read_data(); + uint8_t read_status_hi(); + uint8_t read_data_hi(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write_address_hi(uint8_t data); + void write_data_hi(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal helpers + void update_prescale(uint8_t prescale); + void clock_fm_and_adpcm(); + + // internal state + opn_fidelity m_fidelity; // configured fidelity + uint16_t m_address; // address register + uint8_t m_fm_samples_per_output; // how many samples to repeat + uint8_t m_irq_enable; // IRQ enable register + uint8_t m_flag_control; // flag control register + fm_engine::output_data m_last_fm; // last FM output + fm_engine m_fm; // core FM engine + ssg_engine m_ssg; // SSG engine + ssg_resampler m_ssg_resampler; // SSG resampler helper + adpcm_a_engine m_adpcm_a; // ADPCM-A engine + adpcm_b_engine m_adpcm_b; // ADPCM-B engine +}; + + +// ======================> ymf288 + +class ymf288 +{ +public: + using fm_engine = fm_engine_base; + static constexpr uint32_t FM_OUTPUTS = fm_engine::OUTPUTS; + static constexpr uint32_t SSG_OUTPUTS = 1; + static constexpr uint32_t OUTPUTS = FM_OUTPUTS + SSG_OUTPUTS; + using output_data = ymfm_output; + + // constructor + ymf288(ymfm_interface &intf); + + // configuration + void ssg_override(ssg_override &intf) { m_ssg.override(intf); } + void set_fidelity(opn_fidelity fidelity) { m_fidelity = fidelity; update_prescale(); } + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const + { + switch (m_fidelity) + { + case OPN_FIDELITY_MIN: return input_clock / 144; + case OPN_FIDELITY_MED: return input_clock / 144; + default: + case OPN_FIDELITY_MAX: return input_clock / 16; + } + } + uint32_t ssg_effective_clock(uint32_t input_clock) const { return input_clock / 4; } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read_data(); + uint8_t read_status_hi(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write_address_hi(uint8_t data); + void write_data_hi(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal helpers + bool ymf288_mode() { return ((m_fm.regs().read(0x20) & 0x02) != 0); } + void update_prescale(); + void clock_fm_and_adpcm(); + + // internal state + opn_fidelity m_fidelity; // configured fidelity + uint16_t m_address; // address register + uint8_t m_fm_samples_per_output; // how many samples to repeat + uint8_t m_irq_enable; // IRQ enable register + uint8_t m_flag_control; // flag control register + fm_engine::output_data m_last_fm; // last FM output + fm_engine m_fm; // core FM engine + ssg_engine m_ssg; // SSG engine + ssg_resampler m_ssg_resampler; // SSG resampler helper + adpcm_a_engine m_adpcm_a; // ADPCM-A engine +}; + + +// ======================> ym2610/ym2610b + +class ym2610 +{ + static constexpr uint8_t EOS_FLAGS_MASK = 0xbf; + +public: + using fm_engine = fm_engine_base; + static constexpr uint32_t FM_OUTPUTS = fm_engine::OUTPUTS; + static constexpr uint32_t SSG_OUTPUTS = 1; + static constexpr uint32_t OUTPUTS = FM_OUTPUTS + SSG_OUTPUTS; + using output_data = ymfm_output; + + // constructor + ym2610(ymfm_interface &intf, uint8_t channel_mask = 0x36); + + // configuration + void ssg_override(ssg_override &intf) { m_ssg.override(intf); } + void set_fidelity(opn_fidelity fidelity) { m_fidelity = fidelity; update_prescale(); } + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const + { + switch (m_fidelity) + { + case OPN_FIDELITY_MIN: return input_clock / 144; + case OPN_FIDELITY_MED: return input_clock / 144; + default: + case OPN_FIDELITY_MAX: return input_clock / 16; + } + } + uint32_t ssg_effective_clock(uint32_t input_clock) const { return input_clock / 4; } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read_data(); + uint8_t read_status_hi(); + uint8_t read_data_hi(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write_address_hi(uint8_t data); + void write_data_hi(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal helpers + void update_prescale(); + void clock_fm_and_adpcm(); + + // internal state + opn_fidelity m_fidelity; // configured fidelity + uint16_t m_address; // address register + uint8_t const m_fm_mask; // FM channel mask + uint8_t m_fm_samples_per_output; // how many samples to repeat + uint8_t m_eos_status; // end-of-sample signals + uint8_t m_flag_mask; // flag mask control + fm_engine::output_data m_last_fm; // last FM output + fm_engine m_fm; // core FM engine + ssg_engine m_ssg; // core FM engine + ssg_resampler m_ssg_resampler; // SSG resampler helper + adpcm_a_engine m_adpcm_a; // ADPCM-A engine + adpcm_b_engine m_adpcm_b; // ADPCM-B engine +}; + +class ym2610b : public ym2610 +{ +public: + // constructor + ym2610b(ymfm_interface &intf) : ym2610(intf, 0x3f) { } +}; + + +// ======================> ym2612 + +class ym2612 +{ +public: + using fm_engine = fm_engine_base; + static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS; + using output_data = fm_engine::output_data; + + // constructor + ym2612(ymfm_interface &intf); + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write_address_hi(uint8_t data); + void write_data_hi(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // simulate the DAC discontinuity + constexpr int32_t dac_discontinuity(int32_t value) const { return (value < 0) ? (value - 3) : (value + 4); } + + // internal state + uint16_t m_address; // address register + uint16_t m_dac_data; // 9-bit DAC data + uint8_t m_dac_enable; // DAC enabled? + fm_engine m_fm; // core FM engine +}; + + +// ======================> ym3438 + +class ym3438 : public ym2612 +{ +public: + ym3438(ymfm_interface &intf) : ym2612(intf) { } + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples = 1); +}; + + +// ======================> ymf276 + +class ymf276 : public ym2612 +{ +public: + ymf276(ymfm_interface &intf) : ym2612(intf) { } + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples); +}; + +} + + +#endif // YMFM_OPN_H diff --git a/src/sound/ymfm/ymfm_opq.cpp b/src/sound/ymfm/ymfm_opq.cpp new file mode 100644 index 000000000..3467c0ddf --- /dev/null +++ b/src/sound/ymfm/ymfm_opq.cpp @@ -0,0 +1,480 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "ymfm_opq.h" +#include "ymfm_fm.ipp" + +#define TEMPORARY_DEBUG_PRINTS (0) + +// +// OPQ (aka YM3806/YM3533) +// +// This chip is not officially documented as far as I know. What I have +// comes from Jari Kangas' work on reverse engineering the PSR70: +// +// https://github.com/JKN0/PSR70-reverse +// +// OPQ appears be bsaically a mixture of OPM and OPN. +// + +namespace ymfm +{ + +//********************************************************* +// OPQ SPECIFICS +//********************************************************* + +//------------------------------------------------- +// opq_registers - constructor +//------------------------------------------------- + +opq_registers::opq_registers() : + m_lfo_counter(0), + m_lfo_am(0) +{ + // create the waveforms + for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) + m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15); + + uint16_t zeroval = m_waveform[0][0]; + for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) + m_waveform[1][index] = bitfield(index, 9) ? zeroval : m_waveform[0][index]; +} + + +//------------------------------------------------- +// reset - reset to initial state +//------------------------------------------------- + +void opq_registers::reset() +{ + std::fill_n(&m_regdata[0], REGISTERS, 0); + + // enable output on both channels by default + m_regdata[0x10] = m_regdata[0x11] = m_regdata[0x12] = m_regdata[0x13] = 0xc0; + m_regdata[0x14] = m_regdata[0x15] = m_regdata[0x16] = m_regdata[0x17] = 0xc0; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void opq_registers::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_lfo_counter); + state.save_restore(m_lfo_am); + state.save_restore(m_regdata); +} + + +//------------------------------------------------- +// operator_map - return an array of operator +// indices for each channel; for OPM this is fixed +//------------------------------------------------- + +void opq_registers::operator_map(operator_mapping &dest) const +{ + // seems like the operators are not swizzled like they are on OPM/OPN? + static const operator_mapping s_fixed_map = + { { + operator_list( 0, 8, 16, 24 ), // Channel 0 operators + operator_list( 1, 9, 17, 25 ), // Channel 1 operators + operator_list( 2, 10, 18, 26 ), // Channel 2 operators + operator_list( 3, 11, 19, 27 ), // Channel 3 operators + operator_list( 4, 12, 20, 28 ), // Channel 4 operators + operator_list( 5, 13, 21, 29 ), // Channel 5 operators + operator_list( 6, 14, 22, 30 ), // Channel 6 operators + operator_list( 7, 15, 23, 31 ), // Channel 7 operators + } }; + dest = s_fixed_map; +} + + +//------------------------------------------------- +// write - handle writes to the register array +//------------------------------------------------- + +bool opq_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask) +{ + assert(index < REGISTERS); + + // detune/multiple share a register based on the MSB of what is written + // remap the multiple values to 100-11F + if ((index & 0xe0) == 0x40 && bitfield(data, 7) != 0) + index += 0xc0; + + m_regdata[index] = data; + + // handle writes to the key on index + if (index == 0x05) + { + channel = bitfield(data, 0, 3); + opmask = bitfield(data, 3, 4); + return true; + } + return false; +} + + +//------------------------------------------------- +// clock_noise_and_lfo - clock the noise and LFO, +// handling clock division, depth, and waveform +// computations +//------------------------------------------------- + +int32_t opq_registers::clock_noise_and_lfo() +{ + // OPQ LFO is not well-understood, but the enable and rate values + // look a lot like OPN, so we'll crib from there as a starting point + + // if LFO not enabled (not present on OPN), quick exit with 0s + if (!lfo_enable()) + { + m_lfo_counter = 0; + m_lfo_am = 0; + return 0; + } + + // this table is based on converting the frequencies in the applications + // manual to clock dividers, based on the assumption of a 7-bit LFO value + static uint8_t const lfo_max_count[8] = { 109, 78, 72, 68, 63, 45, 9, 6 }; + uint32_t subcount = uint8_t(m_lfo_counter++); + + // when we cross the divider count, add enough to zero it and cause an + // increment at bit 8; the 7-bit value lives from bits 8-14 + if (subcount >= lfo_max_count[lfo_rate()]) + m_lfo_counter += 0x101 - subcount; + + // AM value is 7 bits, staring at bit 8; grab the low 6 directly + m_lfo_am = bitfield(m_lfo_counter, 8, 6); + + // first half of the AM period (bit 6 == 0) is inverted + if (bitfield(m_lfo_counter, 8+6) == 0) + m_lfo_am ^= 0x3f; + + // PM value is 5 bits, starting at bit 10; grab the low 3 directly + int32_t pm = bitfield(m_lfo_counter, 10, 3); + + // PM is reflected based on bit 3 + if (bitfield(m_lfo_counter, 10+3)) + pm ^= 7; + + // PM is negated based on bit 4 + return bitfield(m_lfo_counter, 10+4) ? -pm : pm; +} + + +//------------------------------------------------- +// lfo_am_offset - return the AM offset from LFO +// for the given channel +//------------------------------------------------- + +uint32_t opq_registers::lfo_am_offset(uint32_t choffs) const +{ + // OPM maps AM quite differently from OPN + + // shift value for AM sensitivity is [*, 0, 1, 2], + // mapping to values of [0, 23.9, 47.8, and 95.6dB] + uint32_t am_sensitivity = ch_lfo_am_sens(choffs); + if (am_sensitivity == 0) + return 0; + + // QUESTION: see OPN note below for the dB range mapping; it applies + // here as well + + // raw LFO AM value on OPM is 0-FF, which is already a factor of 2 + // larger than the OPN below, putting our staring point at 2x theirs; + // this works out since our minimum is 2x their maximum + return m_lfo_am << (am_sensitivity - 1); +} + + +//------------------------------------------------- +// cache_operator_data - fill the operator cache +// with prefetched data +//------------------------------------------------- + +void opq_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache) +{ + // set up the easy stuff + cache.waveform = &m_waveform[op_waveform(opoffs)][0]; + + // get frequency from the appropriate registers + uint32_t block_freq = cache.block_freq = (opoffs & 8) ? ch_block_freq_24(choffs) : ch_block_freq_13(choffs); + + // compute the keycode: block_freq is: + // + // BBBFFFFFFFFFFFF + // ^^^^??? + // + // keycode is not understood, so just guessing it is like OPN: + // the 5-bit keycode uses the top 4 bits plus a magic formula + // for the final bit + uint32_t keycode = bitfield(block_freq, 11, 4) << 1; + + // lowest bit is determined by a mix of next lower FNUM bits + // according to this equation from the YM2608 manual: + // + // (F11 & (F10 | F9 | F8)) | (!F11 & F10 & F9 & F8) + // + // for speed, we just look it up in a 16-bit constant + keycode |= bitfield(0xfe80, bitfield(block_freq, 8, 4)); + + // detune adjustment: the detune values supported by the OPQ are + // a much larger range (6 bits vs 3 bits) compared to any other + // known FM chip; based on experiments, it seems that the extra + // bits provide a bigger detune range rather than finer control, + // so until we get true measurements just assemble a net detune + // value by summing smaller detunes + int32_t detune = int32_t(op_detune(opoffs)) - 0x20; + int32_t abs_detune = std::abs(detune); + int32_t adjust = (abs_detune / 3) * detune_adjustment(3, keycode) + detune_adjustment(abs_detune % 3, keycode); + cache.detune = (detune >= 0) ? adjust : -adjust; + + // multiple value, as an x.1 value (0 means 0.5) + static const uint8_t s_multiple_map[16] = { 1,2,4,6,8,10,12,14,16,18,20,24,30,32,34,36 }; + cache.multiple = s_multiple_map[op_multiple(opoffs)]; + + // phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on + // block_freq, detune, and multiple, so compute it after we've done those + if (lfo_enable() == 0 || ch_lfo_pm_sens(choffs) == 0) + cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0); + else + cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC; + + // total level, scaled by 8 + cache.total_level = op_total_level(opoffs) << 3; + + // 4-bit sustain level, but 15 means 31 so effectively 5 bits + cache.eg_sustain = op_sustain_level(opoffs); + cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10; + cache.eg_sustain <<= 5; + + // determine KSR adjustment for enevlope rates + uint32_t ksrval = keycode >> (op_ksr(opoffs) ^ 3); + cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_SUSTAIN] = effective_rate(op_sustain_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_RELEASE] = effective_rate(op_release_rate(opoffs) * 4 + 2, ksrval); + cache.eg_rate[EG_REVERB] = (ch_reverb(choffs) != 0) ? 5*4 : cache.eg_rate[EG_RELEASE]; + cache.eg_shift = 0; +} + + +//------------------------------------------------- +// compute_phase_step - compute the phase step +//------------------------------------------------- + +uint32_t opq_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm) +{ + // OPN phase calculation has only a single detune parameter + // and uses FNUMs instead of keycodes + + // extract frequency number (low 12 bits of block_freq) + uint32_t fnum = bitfield(cache.block_freq, 0, 12); + + // if there's a non-zero PM sensitivity, compute the adjustment + uint32_t pm_sensitivity = ch_lfo_pm_sens(choffs); + if (pm_sensitivity != 0) + { + // apply the phase adjustment based on the upper 7 bits + // of FNUM and the PM depth parameters + fnum += opn_lfo_pm_phase_adjustment(bitfield(cache.block_freq, 5, 7), pm_sensitivity, lfo_raw_pm); + + // keep fnum to 12 bits + fnum &= 0xfff; + } + + // apply block shift to compute phase step + uint32_t block = bitfield(cache.block_freq, 12, 3); + uint32_t phase_step = (fnum << block) >> 2; + + // apply detune based on the keycode + phase_step += cache.detune; + + // clamp to 17 bits in case detune overflows + // QUESTION: is this specific to the YM2612/3438? + phase_step &= 0x1ffff; + + // apply frequency multiplier (which is cached as an x.1 value) + return (phase_step * cache.multiple) >> 1; +} + + +//------------------------------------------------- +// log_keyon - log a key-on event +//------------------------------------------------- + +std::string opq_registers::log_keyon(uint32_t choffs, uint32_t opoffs) +{ + uint32_t chnum = choffs; + uint32_t opnum = opoffs; + + char buffer[256]; + char *end = &buffer[0]; + + end += sprintf(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, + ch_feedback(choffs), + ch_algorithm(choffs), + op_multiple(opoffs), + op_total_level(opoffs), + op_ksr(opoffs), + op_attack_rate(opoffs), + op_decay_rate(opoffs), + op_sustain_rate(opoffs), + op_release_rate(opoffs), + op_sustain_level(opoffs), + ch_output_0(choffs) ? 'L' : '-', + ch_output_1(choffs) ? 'R' : '-'); + + bool am = (lfo_enable() && op_lfo_am_enable(opoffs) && ch_lfo_am_sens(choffs) != 0); + if (am) + end += sprintf(end, " am=%u", ch_lfo_am_sens(choffs)); + bool pm = (lfo_enable() && ch_lfo_pm_sens(choffs) != 0); + if (pm) + end += sprintf(end, " pm=%u", ch_lfo_pm_sens(choffs)); + if (am || pm) + end += sprintf(end, " lfo=%02X", lfo_rate()); + if (ch_reverb(choffs)) + end += sprintf(end, " reverb"); + + return buffer; +} + + + +//********************************************************* +// YM3806 +//********************************************************* + +//------------------------------------------------- +// ym3806 - constructor +//------------------------------------------------- + +ym3806::ym3806(ymfm_interface &intf) : + m_fm(intf) +{ +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ym3806::reset() +{ + // reset the engines + m_fm.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ym3806::save_restore(ymfm_saved_state &state) +{ + m_fm.save_restore(state); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ym3806::read_status() +{ + uint8_t result = m_fm.status(); + if (m_fm.intf().ymfm_is_busy()) + result |= fm_engine::STATUS_BUSY; + return result; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ym3806::read(uint32_t offset) +{ + uint8_t result = 0xff; + switch (offset) + { + case 0: // status port + result = read_status(); + break; + + default: // unknown + debug::log_unexpected_read_write("Unexpected read from YM3806 offset %02X\n", offset); + break; + } +if (TEMPORARY_DEBUG_PRINTS && offset != 0) printf("Read %02X = %02X\n", offset, result); + return result; +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym3806::write(uint32_t offset, uint8_t data) +{ +if (TEMPORARY_DEBUG_PRINTS && (offset != 3 || data != 0x71)) printf("Write %02X = %02X\n", offset, data); + // write the FM register + m_fm.write(offset, data); +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void ym3806::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // update the FM content; YM3806 is full 14-bit with no intermediate clipping + m_fm.output(output->clear(), 0, 32767, fm_engine::ALL_CHANNELS); + + // YM3608 appears to go through a YM3012 DAC, which means we want to apply + // the FP truncation logic to the outputs + output->roundtrip_fp(); + } +} + +} diff --git a/src/sound/ymfm/ymfm_opq.h b/src/sound/ymfm/ymfm_opq.h new file mode 100644 index 000000000..f530ac070 --- /dev/null +++ b/src/sound/ymfm/ymfm_opq.h @@ -0,0 +1,293 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_OPQ_H +#define YMFM_OPQ_H + +#pragma once + +#include "ymfm.h" +#include "ymfm_fm.h" + +namespace ymfm +{ + +//********************************************************* +// REGISTER CLASSES +//********************************************************* + +// ======================> opq_registers + +// +// OPQ register map: +// +// System-wide registers: +// 03 xxxxxxxx Timer control (unknown; 0x71 causes interrupts at ~10ms) +// 04 ----x--- LFO disable +// -----xxx LFO frequency (0=~4Hz, 6=~10Hz, 7=~47Hz) +// 05 -x------ Key on/off operator 4 +// --x----- Key on/off operator 3 +// ---x---- Key on/off operator 2 +// ----x--- Key on/off operator 1 +// -----xxx Channel select +// +// Per-channel registers (channel in address bits 0-2) +// 10-17 x------- Pan right +// -x------ Pan left +// --xxx--- Feedback level for operator 1 (0-7) +// -----xxx Operator connection algorithm (0-7) +// 18-1F x------- Reverb +// -xxx---- PM sensitivity +// ------xx AM shift +// 20-27 -xxx---- Block (0-7), Operator 2 & 4 +// ----xxxx Frequency number upper 4 bits, Operator 2 & 4 +// 28-2F -xxx---- Block (0-7), Operator 1 & 3 +// ----xxxx Frequency number upper 4 bits, Operator 1 & 3 +// 30-37 xxxxxxxx Frequency number lower 8 bits, Operator 2 & 4 +// 38-3F xxxxxxxx Frequency number lower 8 bits, Operator 1 & 3 +// +// Per-operator registers (channel in address bits 0-2, operator in bits 3-4) +// 40-5F 0-xxxxxx Detune value (0-63) +// 1---xxxx Multiple value (0-15) +// 60-7F -xxxxxxx Total level (0-127) +// 80-9F xx------ Key scale rate (0-3) +// ---xxxxx Attack rate (0-31) +// A0-BF x------- LFO AM enable, retrigger disable +// x------ Waveform select +// ---xxxxx Decay rate (0-31) +// C0-DF ---xxxxx Sustain rate (0-31) +// E0-FF xxxx---- Sustain level (0-15) +// ----xxxx Release rate (0-15) +// +// Diffs from OPM: +// - 2 frequencies/channel +// - retrigger disable +// - 2 waveforms +// - uses FNUM +// - reverb behavior +// - larger detune range +// +// Questions: +// - timer information is pretty light +// - how does echo work? +// - + +class opq_registers : public fm_registers_base +{ +public: + // constants + static constexpr uint32_t OUTPUTS = 2; + static constexpr uint32_t CHANNELS = 8; + static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1; + static constexpr uint32_t OPERATORS = CHANNELS * 4; + static constexpr uint32_t WAVEFORMS = 2; + static constexpr uint32_t REGISTERS = 0x120; + static constexpr uint32_t REG_MODE = 0x03; + static constexpr uint32_t DEFAULT_PRESCALE = 2; + static constexpr uint32_t EG_CLOCK_DIVIDER = 3; + static constexpr bool EG_HAS_REVERB = true; + static constexpr bool MODULATOR_DELAY = false; + static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS; + static constexpr uint8_t STATUS_TIMERA = 0; + static constexpr uint8_t STATUS_TIMERB = 0x04; + static constexpr uint8_t STATUS_BUSY = 0x80; + static constexpr uint8_t STATUS_IRQ = 0; + + // constructor + opq_registers(); + + // reset to initial state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // map channel number to register offset + static constexpr uint32_t channel_offset(uint32_t chnum) + { + assert(chnum < CHANNELS); + return chnum; + } + + // map operator number to register offset + static constexpr uint32_t operator_offset(uint32_t opnum) + { + assert(opnum < OPERATORS); + return opnum; + } + + // return an array of operator indices for each channel + struct operator_mapping { uint32_t chan[CHANNELS]; }; + void operator_map(operator_mapping &dest) const; + + // handle writes to the register array + bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask); + + // clock the noise and LFO, if present, returning LFO PM value + int32_t clock_noise_and_lfo(); + + // reset the LFO + void reset_lfo() { m_lfo_counter = 0; } + + // return the AM offset from LFO for the given channel + uint32_t lfo_am_offset(uint32_t choffs) const; + + // return the current noise state, gated by the noise clock + uint32_t noise_state() const { return 0; } + + // caching helpers + void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache); + + // compute the phase step, given a PM value + uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm); + + // log a key-on event + std::string log_keyon(uint32_t choffs, uint32_t opoffs); + + // system-wide registers + uint32_t timer_a_value() const { return 0; } + uint32_t timer_b_value() const { return byte(0x03, 2, 6) | 0xc0; } // ??? + uint32_t csm() const { return 0; } + uint32_t reset_timer_b() const { return byte(0x03, 0, 1); } // ??? + uint32_t reset_timer_a() const { return 0; } + uint32_t enable_timer_b() const { return byte(0x03, 0, 1); } // ??? + uint32_t enable_timer_a() const { return 0; } + uint32_t load_timer_b() const { return byte(0x03, 0, 1); } // ??? + uint32_t load_timer_a() const { return 0; } + uint32_t lfo_enable() const { return byte(0x04, 3, 1) ^ 1; } + uint32_t lfo_rate() const { return byte(0x04, 0, 3); } + + // per-channel registers + uint32_t ch_output_any(uint32_t choffs) const { return byte(0x10, 6, 2, choffs); } + uint32_t ch_output_0(uint32_t choffs) const { return byte(0x10, 6, 1, choffs); } + uint32_t ch_output_1(uint32_t choffs) const { return byte(0x10, 7, 1, choffs); } + uint32_t ch_output_2(uint32_t choffs) const { return 0; } + uint32_t ch_output_3(uint32_t choffs) const { return 0; } + uint32_t ch_feedback(uint32_t choffs) const { return byte(0x10, 3, 3, choffs); } + uint32_t ch_algorithm(uint32_t choffs) const { return byte(0x10, 0, 3, choffs); } + uint32_t ch_reverb(uint32_t choffs) const { return byte(0x18, 7, 1, choffs); } + uint32_t ch_lfo_pm_sens(uint32_t choffs) const { return byte(0x18, 4, 3, choffs); } + uint32_t ch_lfo_am_sens(uint32_t choffs) const { return byte(0x18, 0, 2, choffs); } + uint32_t ch_block_freq_24(uint32_t choffs) const { return word(0x20, 0, 7, 0x30, 0, 8, choffs); } + uint32_t ch_block_freq_13(uint32_t choffs) const { return word(0x28, 0, 7, 0x38, 0, 8, choffs); } + + // per-operator registers + uint32_t op_detune(uint32_t opoffs) const { return byte(0x40, 0, 6, opoffs); } + uint32_t op_multiple(uint32_t opoffs) const { return byte(0x100, 0, 4, opoffs); } + uint32_t op_total_level(uint32_t opoffs) const { return byte(0x60, 0, 7, opoffs); } + uint32_t op_ksr(uint32_t opoffs) const { return byte(0x80, 6, 2, opoffs); } + uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x80, 0, 5, opoffs); } + uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0xa0, 7, 1, opoffs); } + uint32_t op_waveform(uint32_t opoffs) const { return byte(0xa0, 6, 1, opoffs); } + uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0xa0, 0, 5, opoffs); } + uint32_t op_sustain_rate(uint32_t opoffs) const { return byte(0xc0, 0, 5, opoffs); } + uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0xe0, 4, 4, opoffs); } + uint32_t op_release_rate(uint32_t opoffs) const { return byte(0xe0, 0, 4, opoffs); } + +protected: + // return a bitfield extracted from a byte + uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const + { + return bitfield(m_regdata[offset + extra_offset], start, count); + } + + // return a bitfield extracted from a pair of bytes, MSBs listed first + uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const + { + return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset); + } + + // internal state + uint32_t m_lfo_counter; // LFO counter + uint8_t m_lfo_am; // current LFO AM value + uint8_t m_regdata[REGISTERS]; // register data + uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms +}; + + + +//********************************************************* +// IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ym3806 + +class ym3806 +{ +public: + using fm_engine = fm_engine_base; + static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS; + using output_data = fm_engine::output_data; + + // constructor + ym3806(ymfm_interface &intf); + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data) { /* not supported; only direct writes */ } + void write_data(uint8_t data) { /* not supported; only direct writes */ } + void write(uint32_t offset, uint8_t data); + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal state + fm_engine m_fm; // core FM engine +}; + + +// ======================> ym3533 + +class ym3533 : public ym3806 +{ +public: + // constructor + ym3533(ymfm_interface &intf) : + ym3806(intf) { } +}; + +} + + +#endif // YMFM_OPQ_H diff --git a/src/sound/ymfm/ymfm_opx.h b/src/sound/ymfm/ymfm_opx.h new file mode 100644 index 000000000..9f9bbdba7 --- /dev/null +++ b/src/sound/ymfm/ymfm_opx.h @@ -0,0 +1,290 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_OPX_H +#define YMFM_OPX_H + +#pragma once + +#include "ymfm.h" +#include "ymfm_fm.h" + +namespace ymfm +{ + +//********************************************************* +// REGISTER CLASSES +//********************************************************* + +// ======================> opx_registers + +// +// OPX register map: +// +// System-wide registers: +// +// Per-channel registers (channel in address bits 0-2) +// +// Per-operator registers (4 banks): +// 00-0F x------- Enable +// -xxxx--- EXT out +// -------x Key on +// 10-1F xxxxxxxx LFO frequency +// 20-2F xx------ AM sensitivity (0-3) +// --xxx--- PM sensitivity (0-7) +// ------xx LFO waveform (0=disable, 1=saw, 2= +// 30-3F -xxx---- Detune (0-7) +// ----xxxx Multiple (0-15) +// 40-4F -xxxxxxx Total level (0-127) +// 50-5F xxx----- Key scale (0-7) +// ---xxxxx Attack rate (0-31) +// 60-6F ---xxxxx Decay rate (0-31) +// 70-7F ---xxxxx Sustain rate (0-31) +// 80-8F xxxx---- Sustain level (0-15) +// ----xxxx Release rate (0-15) +// 90-9F xxxxxxxx Frequency number (low 8 bits) +// A0-AF xxxx---- Block (0-15) +// ----xxxx Frequency number (high 4 bits) +// B0-BF x------- Acc on +// -xxx---- Feedback level (0-7) +// -----xxx Waveform (0-7, 7=PCM) +// C0-CF ----xxxx Algorithm (0-15) +// D0-DF xxxx---- CH0 level (0-15) +// ----xxxx CH1 level (0-15) +// E0-EF xxxx---- CH2 level (0-15) +// ----xxxx CH3 level (0-15) +// + +class opx_registers : public fm_registers_base +{ + // LFO waveforms are 256 entries long + static constexpr uint32_t LFO_WAVEFORM_LENGTH = 256; + +public: + // constants + static constexpr uint32_t OUTPUTS = 8; + static constexpr uint32_t CHANNELS = 24; + static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1; + static constexpr uint32_t OPERATORS = CHANNELS * 2; + static constexpr uint32_t WAVEFORMS = 8; + static constexpr uint32_t REGISTERS = 0x800; + static constexpr uint32_t DEFAULT_PRESCALE = 8; + static constexpr uint32_t EG_CLOCK_DIVIDER = 2; + static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS; + static constexpr uint32_t REG_MODE = 0x14; + static constexpr uint8_t STATUS_TIMERA = 0x01; + static constexpr uint8_t STATUS_TIMERB = 0x02; + static constexpr uint8_t STATUS_BUSY = 0x80; + static constexpr uint8_t STATUS_IRQ = 0; + + // constructor + opz_registers(); + + // reset to initial state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // map channel number to register offset + static constexpr uint32_t channel_offset(uint32_t chnum) + { + assert(chnum < CHANNELS); + return chnum; + } + + // map operator number to register offset + static constexpr uint32_t operator_offset(uint32_t opnum) + { + assert(opnum < OPERATORS); + return opnum; + } + + // return an array of operator indices for each channel + struct operator_mapping { uint32_t chan[CHANNELS]; }; + void operator_map(operator_mapping &dest) const; + + // handle writes to the register array + bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask); + + // clock the noise and LFO, if present, returning LFO PM value + int32_t clock_noise_and_lfo(); + + // return the AM offset from LFO for the given channel + uint32_t lfo_am_offset(uint32_t choffs) const; + + // return the current noise state, gated by the noise clock + uint32_t noise_state() const { return m_noise_state; } + + // caching helpers + void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache); + + // compute the phase step, given a PM value + uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm); + + // log a key-on event + std::string log_keyon(uint32_t choffs, uint32_t opoffs); + + // system-wide registers + uint32_t noise_frequency() const { return byte(0x0f, 0, 5); } + uint32_t noise_enable() const { return byte(0x0f, 7, 1); } + uint32_t timer_a_value() const { return word(0x10, 0, 8, 0x11, 0, 2); } + uint32_t timer_b_value() const { return byte(0x12, 0, 8); } + uint32_t csm() const { return byte(0x14, 7, 1); } + uint32_t reset_timer_b() const { return byte(0x14, 5, 1); } + uint32_t reset_timer_a() const { return byte(0x14, 4, 1); } + uint32_t enable_timer_b() const { return byte(0x14, 3, 1); } + uint32_t enable_timer_a() const { return byte(0x14, 2, 1); } + uint32_t load_timer_b() const { return byte(0x14, 1, 1); } + uint32_t load_timer_a() const { return byte(0x14, 0, 1); } + uint32_t lfo2_pm_depth() const { return byte(0x148, 0, 7); } // fake + uint32_t lfo2_rate() const { return byte(0x16, 0, 8); } + uint32_t lfo2_am_depth() const { return byte(0x17, 0, 7); } + uint32_t lfo_rate() const { return byte(0x18, 0, 8); } + uint32_t lfo_am_depth() const { return byte(0x19, 0, 7); } + uint32_t lfo_pm_depth() const { return byte(0x149, 0, 7); } // fake + uint32_t output_bits() const { return byte(0x1b, 6, 2); } + uint32_t lfo2_sync() const { return byte(0x1b, 5, 1); } + uint32_t lfo_sync() const { return byte(0x1b, 4, 1); } + uint32_t lfo2_waveform() const { return byte(0x1b, 2, 2); } + uint32_t lfo_waveform() const { return byte(0x1b, 0, 2); } + + // per-channel registers + uint32_t ch_volume(uint32_t choffs) const { return byte(0x00, 0, 8, choffs); } + uint32_t ch_output_any(uint32_t choffs) const { return byte(0x20, 7, 1, choffs) | byte(0x30, 0, 1, choffs); } + uint32_t ch_output_0(uint32_t choffs) const { return byte(0x30, 0, 1, choffs); } + uint32_t ch_output_1(uint32_t choffs) const { return byte(0x20, 7, 1, choffs) | byte(0x30, 0, 1, choffs); } + uint32_t ch_output_2(uint32_t choffs) const { return 0; } + uint32_t ch_output_3(uint32_t choffs) const { return 0; } + uint32_t ch_key_on(uint32_t choffs) const { return byte(0x20, 6, 1, choffs); } + uint32_t ch_feedback(uint32_t choffs) const { return byte(0x20, 3, 3, choffs); } + uint32_t ch_algorithm(uint32_t choffs) const { return byte(0x20, 0, 3, choffs); } + uint32_t ch_block_freq(uint32_t choffs) const { return word(0x28, 0, 7, 0x30, 2, 6, choffs); } + uint32_t ch_lfo_pm_sens(uint32_t choffs) const { return byte(0x38, 4, 3, choffs); } + uint32_t ch_lfo_am_sens(uint32_t choffs) const { return byte(0x38, 0, 2, choffs); } + uint32_t ch_lfo2_pm_sens(uint32_t choffs) const { return byte(0x140, 4, 3, choffs); } // fake + uint32_t ch_lfo2_am_sens(uint32_t choffs) const { return byte(0x140, 0, 2, choffs); } // fake + + // per-operator registers + uint32_t op_detune(uint32_t opoffs) const { return byte(0x40, 4, 3, opoffs); } + uint32_t op_multiple(uint32_t opoffs) const { return byte(0x40, 0, 4, opoffs); } + uint32_t op_fix_range(uint32_t opoffs) const { return byte(0x40, 4, 3, opoffs); } + uint32_t op_fix_frequency(uint32_t opoffs) const { return byte(0x40, 0, 4, opoffs); } + uint32_t op_waveform(uint32_t opoffs) const { return byte(0x100, 4, 3, opoffs); } // fake + uint32_t op_fine(uint32_t opoffs) const { return byte(0x100, 0, 4, opoffs); } // fake + uint32_t op_total_level(uint32_t opoffs) const { return byte(0x60, 0, 7, opoffs); } + uint32_t op_ksr(uint32_t opoffs) const { return byte(0x80, 6, 2, opoffs); } + uint32_t op_fix_mode(uint32_t opoffs) const { return byte(0x80, 5, 1, opoffs); } + uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x80, 0, 5, opoffs); } + uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0xa0, 7, 1, opoffs); } + uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0xa0, 0, 5, opoffs); } + uint32_t op_detune2(uint32_t opoffs) const { return byte(0xc0, 6, 2, opoffs); } + uint32_t op_sustain_rate(uint32_t opoffs) const { return byte(0xc0, 0, 5, opoffs); } + uint32_t op_eg_shift(uint32_t opoffs) const { return byte(0x120, 6, 2, opoffs); } // fake + uint32_t op_reverb_rate(uint32_t opoffs) const { return byte(0x120, 0, 3, opoffs); } // fake + uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0xe0, 4, 4, opoffs); } + uint32_t op_release_rate(uint32_t opoffs) const { return byte(0xe0, 0, 4, opoffs); } + +protected: + // return a bitfield extracted from a byte + uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const + { + return bitfield(m_regdata[offset + extra_offset], start, count); + } + + // return a bitfield extracted from a pair of bytes, MSBs listed first + uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const + { + return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset); + } + + // internal state + uint32_t m_lfo_counter[2]; // LFO counter + uint32_t m_noise_lfsr; // noise LFSR state + uint8_t m_noise_counter; // noise counter + uint8_t m_noise_state; // latched noise state + uint8_t m_noise_lfo; // latched LFO noise value + uint8_t m_lfo_am[2]; // current LFO AM value + uint8_t m_regdata[REGISTERS]; // register data + uint16_t m_phase_substep[OPERATORS]; // phase substep for fixed frequency + int16_t m_lfo_waveform[4][LFO_WAVEFORM_LENGTH]; // LFO waveforms; AM in low 8, PM in upper 8 + uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms +}; + + + +//********************************************************* +// IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ym2414 + +class ym2414 +{ +public: + using fm_engine = fm_engine_base; + static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS; + using output_data = fm_engine::output_data; + + // constructor + ym2414(ymfm_interface &intf); + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal state + uint8_t m_address; // address register + fm_engine m_fm; // core FM engine +}; + +} + + +#endif // YMFM_OPZ_H diff --git a/src/sound/ymfm/ymfm_opz.cpp b/src/sound/ymfm/ymfm_opz.cpp new file mode 100644 index 000000000..adeefd79f --- /dev/null +++ b/src/sound/ymfm/ymfm_opz.cpp @@ -0,0 +1,808 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "ymfm_opz.h" +#include "ymfm_fm.ipp" + +#define TEMPORARY_DEBUG_PRINTS (0) + +// +// OPZ (aka YM2414) +// +// This chip is not officially documented as far as I know. What I have +// comes from this site: +// +// http://sr4.sakura.ne.jp/fmsound/opz.html +// +// and from reading the TX81Z operator manual, which describes how a number +// of these new features work. +// +// OPZ appears be bsaically OPM with a bunch of extra features. +// +// For starters, there are two LFO generators. I have presumed that they +// operate identically since identical parameters are offered for each. I +// have also presumed the effects are additive between them. The LFOs on +// the OPZ have an extra "sync" option which apparently causes the LFO to +// reset whenever a key on is received. +// +// At the channel level, there is an additional 8-bit volume control. This +// might work as an addition to total level, or some other way. Completely +// unknown, and unimplemented. +// +// At the operator level, there are a number of extra features. First, there +// are 8 different waveforms to choose from. These are different than the +// waveforms introduced in the OPL2 and later chips. +// +// Second, there is an additional "reverb" stage added to the envelope +// generator, which kicks in when the envelope reaches -18dB. It specifies +// a slower decay rate to produce a sort of faux reverb effect. +// +// The envelope generator also supports a 2-bit shift value, which can be +// used to reduce the effect of the envelope attenuation. +// +// OPZ supports a "fixed frequency" mode for each operator, with a 3-bit +// range and 4-bit frequency value, plus a 1-bit enable. Not sure how that +// works at all, so it's not implemented. +// +// There are also several mystery fields in the operators which I have no +// clue about: "fine" (4 bits), "eg_shift" (2 bits), and "rev" (3 bits). +// eg_shift is some kind of envelope generator effect, but how it works is +// unknown. +// +// Also, according to the site above, the panning controls are changed from +// OPM, with a "mono" bit and only one control bit for the right channel. +// Current implementation is just a guess. +// + +namespace ymfm +{ + +//********************************************************* +// OPZ REGISTERS +//********************************************************* + +//------------------------------------------------- +// opz_registers - constructor +//------------------------------------------------- + +opz_registers::opz_registers() : + m_lfo_counter{ 0, 0 }, + m_noise_lfsr(1), + m_noise_counter(0), + m_noise_state(0), + m_noise_lfo(0), + m_lfo_am{ 0, 0 } +{ + // create the waveforms + for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) + m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15); + + // we only have the diagrams to judge from, but suspecting waveform 1 (and + // derived waveforms) are sin^2, based on OPX description of similar wave- + // forms; since our sin table is logarithmic, this ends up just being + // 2*existing value + uint16_t zeroval = m_waveform[0][0]; + for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) + m_waveform[1][index] = std::min(2 * (m_waveform[0][index] & 0x7fff), zeroval) | (bitfield(index, 9) << 15); + + // remaining waveforms are just derivations of the 2 main ones + for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) + { + m_waveform[2][index] = bitfield(index, 9) ? zeroval : m_waveform[0][index]; + m_waveform[3][index] = bitfield(index, 9) ? zeroval : m_waveform[1][index]; + m_waveform[4][index] = bitfield(index, 9) ? zeroval : m_waveform[0][index * 2]; + m_waveform[5][index] = bitfield(index, 9) ? zeroval : m_waveform[1][index * 2]; + m_waveform[6][index] = bitfield(index, 9) ? zeroval : m_waveform[0][(index * 2) & 0x1ff]; + m_waveform[7][index] = bitfield(index, 9) ? zeroval : m_waveform[1][(index * 2) & 0x1ff]; + } + + // create the LFO waveforms; AM in the low 8 bits, PM in the upper 8 + // waveforms are adjusted to match the pictures in the application manual + for (uint32_t index = 0; index < LFO_WAVEFORM_LENGTH; index++) + { + // waveform 0 is a sawtooth + uint8_t am = index ^ 0xff; + int8_t pm = int8_t(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); + 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); + m_lfo_waveform[2][index] = am | (pm << 8); + + // waveform 3 is noise; it is filled in dynamically + } +} + + +//------------------------------------------------- +// reset - reset to initial state +//------------------------------------------------- + +void opz_registers::reset() +{ + std::fill_n(&m_regdata[0], REGISTERS, 0); + + // enable output on both channels by default + m_regdata[0x30] = m_regdata[0x31] = m_regdata[0x32] = m_regdata[0x33] = 0x01; + m_regdata[0x34] = m_regdata[0x35] = m_regdata[0x36] = m_regdata[0x37] = 0x01; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void opz_registers::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_lfo_counter); + state.save_restore(m_lfo_am); + state.save_restore(m_noise_lfsr); + state.save_restore(m_noise_counter); + state.save_restore(m_noise_state); + state.save_restore(m_noise_lfo); + state.save_restore(m_regdata); + state.save_restore(m_phase_substep); +} + + +//------------------------------------------------- +// operator_map - return an array of operator +// indices for each channel; for OPZ this is fixed +//------------------------------------------------- + +void opz_registers::operator_map(operator_mapping &dest) const +{ + // Note that the channel index order is 0,2,1,3, so we bitswap the index. + // + // This is because the order in the map is: + // carrier 1, carrier 2, modulator 1, modulator 2 + // + // But when wiring up the connections, the more natural order is: + // carrier 1, modulator 1, carrier 2, modulator 2 + static const operator_mapping s_fixed_map = + { { + operator_list( 0, 16, 8, 24 ), // Channel 0 operators + operator_list( 1, 17, 9, 25 ), // Channel 1 operators + operator_list( 2, 18, 10, 26 ), // Channel 2 operators + operator_list( 3, 19, 11, 27 ), // Channel 3 operators + operator_list( 4, 20, 12, 28 ), // Channel 4 operators + operator_list( 5, 21, 13, 29 ), // Channel 5 operators + operator_list( 6, 22, 14, 30 ), // Channel 6 operators + operator_list( 7, 23, 15, 31 ), // Channel 7 operators + } }; + dest = s_fixed_map; +} + + +//------------------------------------------------- +// write - handle writes to the register array +//------------------------------------------------- + +bool opz_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask) +{ + assert(index < REGISTERS); + + // special mappings: + // 0x16 -> 0x188 if bit 7 is set + // 0x19 -> 0x189 if bit 7 is set + // 0x38..0x3F -> 0x180..0x187 if bit 7 is set + // 0x40..0x5F -> 0x100..0x11F if bit 7 is set + // 0xC0..0xDF -> 0x120..0x13F if bit 5 is set + if (index == 0x17 && bitfield(data, 7) != 0) + m_regdata[0x188] = data; + else if (index == 0x19 && bitfield(data, 7) != 0) + m_regdata[0x189] = data; + else if ((index & 0xf8) == 0x38 && bitfield(data, 7) != 0) + m_regdata[0x180 + (index & 7)] = data; + else if ((index & 0xe0) == 0x40 && bitfield(data, 7) != 0) + m_regdata[0x100 + (index & 0x1f)] = data; + else if ((index & 0xe0) == 0xc0 && bitfield(data, 5) != 0) + m_regdata[0x120 + (index & 0x1f)] = data; + else if (index < 0x100) + m_regdata[index] = data; + + // preset writes restore some values from a preset memory; not sure + // how this really works but the TX81Z will overwrite the sustain level/ + // release rate register and the envelope shift/reverb rate register to + // dampen sound, then write the preset number to register 8 to restore them + if (index == 0x08) + { + int chan = bitfield(data, 0, 3); + if (TEMPORARY_DEBUG_PRINTS) + printf("Loading preset %d\n", chan); + m_regdata[0xe0 + chan + 0] = m_regdata[0x140 + chan + 0]; + m_regdata[0xe0 + chan + 8] = m_regdata[0x140 + chan + 8]; + m_regdata[0xe0 + chan + 16] = m_regdata[0x140 + chan + 16]; + m_regdata[0xe0 + chan + 24] = m_regdata[0x140 + chan + 24]; + m_regdata[0x120 + chan + 0] = m_regdata[0x160 + chan + 0]; + m_regdata[0x120 + chan + 8] = m_regdata[0x160 + chan + 8]; + m_regdata[0x120 + chan + 16] = m_regdata[0x160 + chan + 16]; + m_regdata[0x120 + chan + 24] = m_regdata[0x160 + chan + 24]; + } + + // store the presets under some unknown condition; the pattern of writes + // when setting a new preset is: + // + // 08 (0-7), 80-9F, A0-BF, C0-DF, C0-DF (alt), 20-27, 40-5F, 40-5F (alt), + // C0-DF (alt -- again?), 38-3F, 1B, 18, E0-FF + // + // So it writes 0-7 to 08 to either reset all presets or to indicate + // that we're going to be loading them. Immediately after all the writes + // above, the very next write will be temporary values to blow away the + // values loaded into E0-FF, so somehow it also knows that anything after + // that point is not part of the preset. + // + // For now, try using the 40-5F (alt) writes as flags that presets are + // being loaded until the E0-FF writes happen. + bool is_setting_preset = (bitfield(m_regdata[0x100 + (index & 0x1f)], 7) != 0); + if (is_setting_preset) + { + if ((index & 0xe0) == 0xe0) + { + m_regdata[0x140 + (index & 0x1f)] = data; + m_regdata[0x100 + (index & 0x1f)] &= 0x7f; + } + else if ((index & 0xe0) == 0xc0 && bitfield(data, 5) != 0) + m_regdata[0x160 + (index & 0x1f)] = data; + } + + // handle writes to the key on index + if ((index & 0xf8) == 0x20 && bitfield(index, 0, 3) == bitfield(m_regdata[0x08], 0, 3)) + { + channel = bitfield(index, 0, 3); + opmask = ch_key_on(channel) ? 0xf : 0; + + // according to the TX81Z manual, the sync option causes the LFOs + // to reset at each note on + if (opmask != 0) + { + if (lfo_sync()) + m_lfo_counter[0] = 0; + if (lfo2_sync()) + m_lfo_counter[1] = 0; + } + return true; + } + return false; +} + + +//------------------------------------------------- +// clock_noise_and_lfo - clock the noise and LFO, +// handling clock division, depth, and waveform +// computations +//------------------------------------------------- + +int32_t opz_registers::clock_noise_and_lfo() +{ + // base noise frequency is measured at 2x 1/2 FM frequency; this + // means each tick counts as two steps against the noise counter + uint32_t freq = noise_frequency(); + for (int rep = 0; rep < 2; rep++) + { + // evidence seems to suggest the LFSR is clocked continually and just + // sampled at the noise frequency for output purposes; note that the + // low 8 bits are the most recent 8 bits of history while bits 8-24 + // contain the 17 bit LFSR state + m_noise_lfsr <<= 1; + m_noise_lfsr |= bitfield(m_noise_lfsr, 17) ^ bitfield(m_noise_lfsr, 14) ^ 1; + + // compare against the frequency and latch when we exceed it + if (m_noise_counter++ >= freq) + { + m_noise_counter = 0; + m_noise_state = bitfield(m_noise_lfsr, 17); + } + } + + // treat the rate as a 4.4 floating-point step value with implied + // leading 1; this matches exactly the frequencies in the application + // manual, though it might not be implemented exactly this way on chip + uint32_t rate0 = lfo_rate(); + uint32_t rate1 = lfo2_rate(); + m_lfo_counter[0] += (0x10 | bitfield(rate0, 0, 4)) << bitfield(rate0, 4, 4); + m_lfo_counter[1] += (0x10 | bitfield(rate1, 0, 4)) << bitfield(rate1, 4, 4); + uint32_t lfo0 = bitfield(m_lfo_counter[0], 22, 8); + uint32_t lfo1 = bitfield(m_lfo_counter[1], 22, 8); + + // fill in the noise entry 1 ahead of our current position; this + // ensures the current value remains stable for a full LFO clock + // and effectively latches the running value when the LFO advances + uint32_t lfo_noise = bitfield(m_noise_lfsr, 17, 8); + m_lfo_waveform[3][(lfo0 + 1) & 0xff] = lfo_noise | (lfo_noise << 8); + m_lfo_waveform[3][(lfo1 + 1) & 0xff] = lfo_noise | (lfo_noise << 8); + + // fetch the AM/PM values based on the waveform; AM is unsigned and + // encoded in the low 8 bits, while PM signed and encoded in the upper + // 8 bits + int32_t ampm0 = m_lfo_waveform[lfo_waveform()][lfo0]; + int32_t ampm1 = m_lfo_waveform[lfo2_waveform()][lfo1]; + + // apply depth to the AM values and store for later + m_lfo_am[0] = ((ampm0 & 0xff) * lfo_am_depth()) >> 7; + m_lfo_am[1] = ((ampm1 & 0xff) * lfo2_am_depth()) >> 7; + + // apply depth to the PM values and return them combined into two + int32_t pm0 = ((ampm0 >> 8) * int32_t(lfo_pm_depth())) >> 7; + int32_t pm1 = ((ampm1 >> 8) * int32_t(lfo2_pm_depth())) >> 7; + return (pm0 & 0xff) | (pm1 << 8); +} + + +//------------------------------------------------- +// lfo_am_offset - return the AM offset from LFO +// for the given channel +//------------------------------------------------- + +uint32_t opz_registers::lfo_am_offset(uint32_t choffs) const +{ + // not sure how this works for real, but just adding the two + // AM LFOs together + uint32_t result = 0; + + // shift value for AM sensitivity is [*, 0, 1, 2], + // mapping to values of [0, 23.9, 47.8, and 95.6dB] + uint32_t am_sensitivity = ch_lfo_am_sens(choffs); + if (am_sensitivity != 0) + result = m_lfo_am[0] << (am_sensitivity - 1); + + // QUESTION: see OPN note below for the dB range mapping; it applies + // here as well + + // raw LFO AM value on OPZ is 0-FF, which is already a factor of 2 + // larger than the OPN below, putting our staring point at 2x theirs; + // this works out since our minimum is 2x their maximum + uint32_t am_sensitivity2 = ch_lfo2_am_sens(choffs); + if (am_sensitivity2 != 0) + result += m_lfo_am[1] << (am_sensitivity2 - 1); + + return result; +} + + +//------------------------------------------------- +// cache_operator_data - fill the operator cache +// with prefetched data +//------------------------------------------------- + +void opz_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache) +{ + // TODO: how does fixed frequency mode work? appears to be enabled by + // op_fix_mode(), and controlled by op_fix_range(), op_fix_frequency() + + // TODO: what is op_rev()? + + // set up the easy stuff + cache.waveform = &m_waveform[op_waveform(opoffs)][0]; + + // get frequency from the channel + uint32_t block_freq = cache.block_freq = ch_block_freq(choffs); + + // compute the keycode: block_freq is: + // + // BBBCCCCFFFFFF + // ^^^^^ + // + // the 5-bit keycode is just the top 5 bits (block + top 2 bits + // of the key code) + uint32_t keycode = bitfield(block_freq, 8, 5); + + // detune adjustment + cache.detune = detune_adjustment(op_detune(opoffs), keycode); + + // multiple value, as an x.4 value (0 means 0.5) + // the "fine" control provides the fractional bits + cache.multiple = op_multiple(opoffs) << 4; + if (cache.multiple == 0) + cache.multiple = 0x08; + cache.multiple |= op_fine(opoffs); + + // phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on + // block_freq, detune, and multiple, so compute it after we've done those; + // note that fix frequency mode is also treated as dynamic + if (!op_fix_mode(opoffs) && (lfo_pm_depth() == 0 || ch_lfo_pm_sens(choffs) == 0) && (lfo2_pm_depth() == 0 || ch_lfo2_pm_sens(choffs) == 0)) + cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0); + else + cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC; + + // total level, scaled by 8 + // TODO: how does ch_volume() fit into this? + cache.total_level = op_total_level(opoffs) << 3; + + // 4-bit sustain level, but 15 means 31 so effectively 5 bits + cache.eg_sustain = op_sustain_level(opoffs); + cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10; + cache.eg_sustain <<= 5; + + // determine KSR adjustment for enevlope rates + uint32_t ksrval = keycode >> (op_ksr(opoffs) ^ 3); + cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_SUSTAIN] = effective_rate(op_sustain_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_RELEASE] = effective_rate(op_release_rate(opoffs) * 4 + 2, ksrval); + cache.eg_rate[EG_REVERB] = cache.eg_rate[EG_RELEASE]; + uint32_t reverb = op_reverb_rate(opoffs); + if (reverb != 0) + cache.eg_rate[EG_REVERB] = std::min(effective_rate(reverb * 4 + 2, ksrval), cache.eg_rate[EG_REVERB]); + + // set the envelope shift; TX81Z manual says operator 1 shift is fixed at "off" + cache.eg_shift = ((opoffs & 0x18) == 0) ? 0 : op_eg_shift(opoffs); +} + + +//------------------------------------------------- +// compute_phase_step - compute the phase step +//------------------------------------------------- + +uint32_t opz_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm) +{ + // OPZ has a fixed frequency mode; it is unclear whether the + // detune and multiple parameters affect things + + uint32_t phase_step; + if (op_fix_mode(opoffs)) + { + // the baseline frequency in hz comes from the fix frequency and fine + // registers, which can specify values 8-255Hz in 1Hz increments; that + // value is then shifted up by the 3-bit range + uint32_t freq = op_fix_frequency(opoffs) << 4; + if (freq == 0) + freq = 8; + freq |= op_fine(opoffs); + freq <<= op_fix_range(opoffs); + + // there is not enough resolution in the plain phase step to track the + // full range of frequencies, so we keep a per-operator sub step with an + // additional 12 bits of resolution; this calculation gives us, for + // example, a frequency of 8.0009Hz when 8Hz is requested + uint32_t substep = m_phase_substep[opoffs]; + substep += 75 * freq; + phase_step = substep >> 12; + m_phase_substep[opoffs] = substep & 0xfff; + + // detune/multiple occupy the same space as fix_range/fix_frequency so + // don't apply them in addition + return phase_step; + } + else + { + // start with coarse detune delta; table uses cents value from + // manual, converted into 1/64ths + static const int16_t s_detune2_delta[4] = { 0, (600*64+50)/100, (781*64+50)/100, (950*64+50)/100 }; + int32_t delta = s_detune2_delta[op_detune2(opoffs)]; + + // add in the PM deltas + uint32_t pm_sensitivity = ch_lfo_pm_sens(choffs); + if (pm_sensitivity != 0) + { + // raw PM value is -127..128 which is +/- 200 cents + // manual gives these magnitudes in cents: + // 0, +/-5, +/-10, +/-20, +/-50, +/-100, +/-400, +/-700 + // this roughly corresponds to shifting the 200-cent value: + // 0 >> 5, >> 4, >> 3, >> 2, >> 1, << 1, << 2 + if (pm_sensitivity < 6) + delta += int8_t(lfo_raw_pm) >> (6 - pm_sensitivity); + else + delta += int8_t(lfo_raw_pm) << (pm_sensitivity - 5); + } + uint32_t pm_sensitivity2 = ch_lfo2_pm_sens(choffs); + if (pm_sensitivity2 != 0) + { + // raw PM value is -127..128 which is +/- 200 cents + // manual gives these magnitudes in cents: + // 0, +/-5, +/-10, +/-20, +/-50, +/-100, +/-400, +/-700 + // this roughly corresponds to shifting the 200-cent value: + // 0 >> 5, >> 4, >> 3, >> 2, >> 1, << 1, << 2 + if (pm_sensitivity2 < 6) + delta += int8_t(lfo_raw_pm >> 8) >> (6 - pm_sensitivity2); + else + delta += int8_t(lfo_raw_pm >> 8) << (pm_sensitivity2 - 5); + } + + // apply delta and convert to a frequency number; this translation is + // the same as OPM so just re-use that helper + phase_step = opm_key_code_to_phase_step(cache.block_freq, delta); + + // apply detune based on the keycode + phase_step += cache.detune; + + // apply frequency multiplier (which is cached as an x.4 value) + return (phase_step * cache.multiple) >> 4; + } +} + + +//------------------------------------------------- +// log_keyon - log a key-on event +//------------------------------------------------- + +std::string opz_registers::log_keyon(uint32_t choffs, uint32_t opoffs) +{ + uint32_t chnum = choffs; + uint32_t opnum = opoffs; + + char buffer[256]; + char *end = &buffer[0]; + + end += sprintf(end, "%u.%02u", chnum, opnum); + + if (op_fix_mode(opoffs)) + end += sprintf(end, " fixfreq=%X fine=%X shift=%X", op_fix_frequency(opoffs), op_fine(opoffs), op_fix_range(opoffs)); + else + end += sprintf(end, " freq=%04X dt2=%u fine=%X", ch_block_freq(choffs), op_detune2(opoffs), op_fine(opoffs)); + + end += sprintf(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), + op_multiple(opoffs), + op_total_level(opoffs), + op_ksr(opoffs), + op_attack_rate(opoffs), + op_decay_rate(opoffs), + op_sustain_rate(opoffs), + op_release_rate(opoffs), + op_sustain_level(opoffs), + ch_output_0(choffs) ? 'L' : '-', + ch_output_1(choffs) ? 'R' : '-'); + + if (op_eg_shift(opoffs) != 0) + end += sprintf(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 += sprintf(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 += sprintf(end, " pm=%u/%02X", ch_lfo_pm_sens(choffs), lfo_pm_depth()); + if (am || pm) + end += sprintf(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 += sprintf(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 += sprintf(end, " pm2=%u/%02X", ch_lfo2_pm_sens(choffs), lfo2_pm_depth()); + if (am2 || pm2) + end += sprintf(end, " lfo2=%02X/%c", lfo2_rate(), "WQTN"[lfo2_waveform()]); + + if (op_reverb_rate(opoffs) != 0) + end += sprintf(end, " rev=%u", op_reverb_rate(opoffs)); + if (op_waveform(opoffs) != 0) + end += sprintf(end, " wf=%u", op_waveform(opoffs)); + if (noise_enable() && opoffs == 31) + end += sprintf(end, " noise=1"); + + return buffer; +} + + + +//********************************************************* +// YM2414 +//********************************************************* + +//------------------------------------------------- +// ym2414 - constructor +//------------------------------------------------- + +ym2414::ym2414(ymfm_interface &intf) : + m_address(0), + m_fm(intf) +{ +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ym2414::reset() +{ + // reset the engines + m_fm.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ym2414::save_restore(ymfm_saved_state &state) +{ + m_fm.save_restore(state); + state.save_restore(m_address); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ym2414::read_status() +{ + uint8_t result = m_fm.status(); + if (m_fm.intf().ymfm_is_busy()) + result |= fm_engine::STATUS_BUSY; + return result; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ym2414::read(uint32_t offset) +{ + uint8_t result = 0xff; + switch (offset & 1) + { + case 0: // data port (unused) + debug::log_unexpected_read_write("Unexpected read from YM2414 offset %d\n", offset & 3); + break; + + case 1: // status port, YM2203 compatible + result = read_status(); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ym2414::write_address(uint8_t data) +{ + // just set the address + m_address = data; +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym2414::write_data(uint8_t data) +{ + // write the FM register + m_fm.write(m_address, data); + if (TEMPORARY_DEBUG_PRINTS) + { + switch (m_address & 0xe0) + { + case 0x00: + printf("CTL %02X = %02X\n", m_address, data); + break; + + case 0x20: + switch (m_address & 0xf8) + { + case 0x20: printf("R/FBL/ALG %d = %02X\n", m_address & 7, data); break; + case 0x28: printf("KC %d = %02X\n", m_address & 7, data); break; + case 0x30: printf("KF/M %d = %02X\n", m_address & 7, data); break; + case 0x38: printf("PMS/AMS %d = %02X\n", m_address & 7, data); break; + } + break; + + case 0x40: + if (bitfield(data, 7) == 0) + printf("DT1/MUL %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + else + printf("OW/FINE %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + break; + + case 0x60: + printf("TL %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + break; + + case 0x80: + printf("KRS/FIX/AR %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + break; + + case 0xa0: + printf("A/D1R %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + break; + + case 0xc0: + if (bitfield(data, 5) == 0) + printf("DT2/D2R %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + else + printf("EGS/REV %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + break; + + case 0xe0: + printf("D1L/RR %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + break; + } + } + + // special cases + if (m_address == 0x1b) + { + // writes to register 0x1B send the upper 2 bits to the output lines + m_fm.intf().ymfm_external_write(ACCESS_IO, 0, data >> 6); + } + + // mark busy for a bit + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym2414::write(uint32_t offset, uint8_t data) +{ + switch (offset & 1) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void ym2414::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // update the FM content; YM2414 is full 14-bit with no intermediate clipping + m_fm.output(output->clear(), 0, 32767, fm_engine::ALL_CHANNELS); + + // unsure about YM2414 outputs; assume it is like YM2151 + output->roundtrip_fp(); + } +} + +} diff --git a/src/sound/ymfm/ymfm_opz.h b/src/sound/ymfm/ymfm_opz.h new file mode 100644 index 000000000..997ba32f9 --- /dev/null +++ b/src/sound/ymfm/ymfm_opz.h @@ -0,0 +1,332 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_OPZ_H +#define YMFM_OPZ_H + +#pragma once + +#include "ymfm.h" +#include "ymfm_fm.h" + +namespace ymfm +{ + +//********************************************************* +// REGISTER CLASSES +//********************************************************* + +// ======================> opz_registers + +// +// OPZ register map: +// +// System-wide registers: +// 08 -----xxx Load preset (not sure how it gets saved) +// 0F x------- Noise enable +// ---xxxxx Noise frequency +// 10 xxxxxxxx Timer A value (upper 8 bits) +// 11 ------xx Timer A value (lower 2 bits) +// 12 xxxxxxxx Timer B value +// 14 x------- CSM mode +// --x----- Reset timer B +// ---x---- Reset timer A +// ----x--- Enable timer B +// -----x-- Enable timer A +// ------x- Load timer B +// -------x Load timer A +// 16 xxxxxxxx LFO #2 frequency +// 17 0xxxxxxx AM LFO #2 depth +// 1xxxxxxx PM LFO #2 depth +// 18 xxxxxxxx LFO frequency +// 19 0xxxxxxx AM LFO depth +// 1xxxxxxx PM LFO depth +// 1B xx------ CT (2 output data lines) +// --x----- LFO #2 sync +// ---x---- LFO sync +// ----xx-- LFO #2 waveform +// ------xx LFO waveform +// +// Per-channel registers (channel in address bits 0-2) +// 00-07 xxxxxxxx Channel volume +// 20-27 x------- Pan right +// -x------ Key on (0)/off(1) +// --xxx--- Feedback level for operator 1 (0-7) +// -----xxx Operator connection algorithm (0-7) +// 28-2F -xxxxxxx Key code +// 30-37 xxxxxx-- Key fraction +// -------x Mono? mode +// 38-3F 0xxx---- LFO PM sensitivity +// -----0xx LFO AM shift +// 1xxx---- LFO #2 PM sensitivity +// -----1xx LFO #2 AM shift +// +// Per-operator registers (channel in address bits 0-2, operator in bits 3-4) +// 40-5F 0xxx---- Detune value (0-7) +// 0---xxxx Multiple value (0-15) +// 0xxx---- Fix range (0-15) +// 0---xxxx Fix frequency (0-15) +// 1xxx---- Oscillator waveform (0-7) +// 1---xxxx Fine? (0-15) +// 60-7F -xxxxxxx Total level (0-127) +// 80-9F xx------ Key scale rate (0-3) +// --x----- Fix frequency mode +// ---xxxxx Attack rate (0-31) +// A0-BF x------- LFO AM enable +// ---xxxxx Decay rate (0-31) +// C0-DF xx0----- Detune 2 value (0-3) +// --0xxxxx Sustain rate (0-31) +// xx1----- Envelope generator shift? (0-3) +// --1--xxx Rev? (0-7) +// E0-FF xxxx---- Sustain level (0-15) +// ----xxxx Release rate (0-15) +// +// Internal (fake) registers: +// 100-11F -xxx---- Oscillator waveform (0-7) +// ----xxxx Fine? (0-15) +// 120-13F xx------ Envelope generator shift (0-3) +// -----xxx Reverb rate (0-7) +// 140-15F xxxx---- Preset sustain level (0-15) +// ----xxxx Preset release rate (0-15) +// 160-17F xx------ Envelope generator shift (0-3) +// -----xxx Reverb rate (0-7) +// 180-187 -xxx---- LFO #2 PM sensitivity +// ---- xxx LFO #2 AM shift +// 188 -xxxxxxx LFO #2 PM depth +// 189 -xxxxxxx LFO PM depth +// + +class opz_registers : public fm_registers_base +{ + // LFO waveforms are 256 entries long + static constexpr uint32_t LFO_WAVEFORM_LENGTH = 256; + +public: + // constants + static constexpr uint32_t OUTPUTS = 2; + static constexpr uint32_t CHANNELS = 8; + static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1; + static constexpr uint32_t OPERATORS = CHANNELS * 4; + static constexpr uint32_t WAVEFORMS = 8; + static constexpr uint32_t REGISTERS = 0x190; + static constexpr uint32_t DEFAULT_PRESCALE = 2; + static constexpr uint32_t EG_CLOCK_DIVIDER = 3; + static constexpr bool EG_HAS_REVERB = true; + static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS; + static constexpr uint32_t REG_MODE = 0x14; + static constexpr uint8_t STATUS_TIMERA = 0x01; + static constexpr uint8_t STATUS_TIMERB = 0x02; + static constexpr uint8_t STATUS_BUSY = 0x80; + static constexpr uint8_t STATUS_IRQ = 0; + + // constructor + opz_registers(); + + // reset to initial state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // map channel number to register offset + static constexpr uint32_t channel_offset(uint32_t chnum) + { + assert(chnum < CHANNELS); + return chnum; + } + + // map operator number to register offset + static constexpr uint32_t operator_offset(uint32_t opnum) + { + assert(opnum < OPERATORS); + return opnum; + } + + // return an array of operator indices for each channel + struct operator_mapping { uint32_t chan[CHANNELS]; }; + void operator_map(operator_mapping &dest) const; + + // handle writes to the register array + bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask); + + // clock the noise and LFO, if present, returning LFO PM value + int32_t clock_noise_and_lfo(); + + // return the AM offset from LFO for the given channel + uint32_t lfo_am_offset(uint32_t choffs) const; + + // return the current noise state, gated by the noise clock + uint32_t noise_state() const { return m_noise_state; } + + // caching helpers + void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache); + + // compute the phase step, given a PM value + uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm); + + // log a key-on event + std::string log_keyon(uint32_t choffs, uint32_t opoffs); + + // system-wide registers + uint32_t noise_frequency() const { return byte(0x0f, 0, 5); } + uint32_t noise_enable() const { return byte(0x0f, 7, 1); } + uint32_t timer_a_value() const { return word(0x10, 0, 8, 0x11, 0, 2); } + uint32_t timer_b_value() const { return byte(0x12, 0, 8); } + uint32_t csm() const { return byte(0x14, 7, 1); } + uint32_t reset_timer_b() const { return byte(0x14, 5, 1); } + uint32_t reset_timer_a() const { return byte(0x14, 4, 1); } + uint32_t enable_timer_b() const { return byte(0x14, 3, 1); } + uint32_t enable_timer_a() const { return byte(0x14, 2, 1); } + uint32_t load_timer_b() const { return byte(0x14, 1, 1); } + uint32_t load_timer_a() const { return byte(0x14, 0, 1); } + uint32_t lfo2_pm_depth() const { return byte(0x188, 0, 7); } // fake + uint32_t lfo2_rate() const { return byte(0x16, 0, 8); } + uint32_t lfo2_am_depth() const { return byte(0x17, 0, 7); } + uint32_t lfo_rate() const { return byte(0x18, 0, 8); } + uint32_t lfo_am_depth() const { return byte(0x19, 0, 7); } + uint32_t lfo_pm_depth() const { return byte(0x189, 0, 7); } // fake + uint32_t output_bits() const { return byte(0x1b, 6, 2); } + uint32_t lfo2_sync() const { return byte(0x1b, 5, 1); } + uint32_t lfo_sync() const { return byte(0x1b, 4, 1); } + uint32_t lfo2_waveform() const { return byte(0x1b, 2, 2); } + uint32_t lfo_waveform() const { return byte(0x1b, 0, 2); } + + // per-channel registers + uint32_t ch_volume(uint32_t choffs) const { return byte(0x00, 0, 8, choffs); } + uint32_t ch_output_any(uint32_t choffs) const { return byte(0x20, 7, 1, choffs) | byte(0x30, 0, 1, choffs); } + uint32_t ch_output_0(uint32_t choffs) const { return byte(0x30, 0, 1, choffs); } + uint32_t ch_output_1(uint32_t choffs) const { return byte(0x20, 7, 1, choffs) | byte(0x30, 0, 1, choffs); } + uint32_t ch_output_2(uint32_t choffs) const { return 0; } + uint32_t ch_output_3(uint32_t choffs) const { return 0; } + uint32_t ch_key_on(uint32_t choffs) const { return byte(0x20, 6, 1, choffs); } + uint32_t ch_feedback(uint32_t choffs) const { return byte(0x20, 3, 3, choffs); } + uint32_t ch_algorithm(uint32_t choffs) const { return byte(0x20, 0, 3, choffs); } + uint32_t ch_block_freq(uint32_t choffs) const { return word(0x28, 0, 7, 0x30, 2, 6, choffs); } + uint32_t ch_lfo_pm_sens(uint32_t choffs) const { return byte(0x38, 4, 3, choffs); } + uint32_t ch_lfo_am_sens(uint32_t choffs) const { return byte(0x38, 0, 2, choffs); } + uint32_t ch_lfo2_pm_sens(uint32_t choffs) const { return byte(0x180, 4, 3, choffs); } // fake + uint32_t ch_lfo2_am_sens(uint32_t choffs) const { return byte(0x180, 0, 2, choffs); } // fake + + // per-operator registers + uint32_t op_detune(uint32_t opoffs) const { return byte(0x40, 4, 3, opoffs); } + uint32_t op_multiple(uint32_t opoffs) const { return byte(0x40, 0, 4, opoffs); } + uint32_t op_fix_range(uint32_t opoffs) const { return byte(0x40, 4, 3, opoffs); } + uint32_t op_fix_frequency(uint32_t opoffs) const { return byte(0x40, 0, 4, opoffs); } + uint32_t op_waveform(uint32_t opoffs) const { return byte(0x100, 4, 3, opoffs); } // fake + uint32_t op_fine(uint32_t opoffs) const { return byte(0x100, 0, 4, opoffs); } // fake + uint32_t op_total_level(uint32_t opoffs) const { return byte(0x60, 0, 7, opoffs); } + uint32_t op_ksr(uint32_t opoffs) const { return byte(0x80, 6, 2, opoffs); } + uint32_t op_fix_mode(uint32_t opoffs) const { return byte(0x80, 5, 1, opoffs); } + uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x80, 0, 5, opoffs); } + uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0xa0, 7, 1, opoffs); } + uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0xa0, 0, 5, opoffs); } + uint32_t op_detune2(uint32_t opoffs) const { return byte(0xc0, 6, 2, opoffs); } + uint32_t op_sustain_rate(uint32_t opoffs) const { return byte(0xc0, 0, 5, opoffs); } + uint32_t op_eg_shift(uint32_t opoffs) const { return byte(0x120, 6, 2, opoffs); } // fake + uint32_t op_reverb_rate(uint32_t opoffs) const { return byte(0x120, 0, 3, opoffs); } // fake + uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0xe0, 4, 4, opoffs); } + uint32_t op_release_rate(uint32_t opoffs) const { return byte(0xe0, 0, 4, opoffs); } + +protected: + // return a bitfield extracted from a byte + uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const + { + return bitfield(m_regdata[offset + extra_offset], start, count); + } + + // return a bitfield extracted from a pair of bytes, MSBs listed first + uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const + { + return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset); + } + + // internal state + uint32_t m_lfo_counter[2]; // LFO counter + uint32_t m_noise_lfsr; // noise LFSR state + uint8_t m_noise_counter; // noise counter + uint8_t m_noise_state; // latched noise state + uint8_t m_noise_lfo; // latched LFO noise value + uint8_t m_lfo_am[2]; // current LFO AM value + uint8_t m_regdata[REGISTERS]; // register data + uint16_t m_phase_substep[OPERATORS]; // phase substep for fixed frequency + int16_t m_lfo_waveform[4][LFO_WAVEFORM_LENGTH]; // LFO waveforms; AM in low 8, PM in upper 8 + uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms +}; + + + +//********************************************************* +// IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ym2414 + +class ym2414 +{ +public: + using fm_engine = fm_engine_base; + static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS; + using output_data = fm_engine::output_data; + + // constructor + ym2414(ymfm_interface &intf); + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal state + uint8_t m_address; // address register + fm_engine m_fm; // core FM engine +}; + +} + + +#endif // YMFM_OPZ_H diff --git a/src/sound/ymfm/ymfm_pcm.cpp b/src/sound/ymfm/ymfm_pcm.cpp new file mode 100644 index 000000000..50595133b --- /dev/null +++ b/src/sound/ymfm/ymfm_pcm.cpp @@ -0,0 +1,715 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "ymfm_pcm.h" +#include "ymfm_fm.h" +#include "ymfm_fm.ipp" + +namespace ymfm +{ + +//********************************************************* +// PCM REGISTERS +//********************************************************* + +//------------------------------------------------- +// reset - reset the register state +//------------------------------------------------- + +void pcm_registers::reset() +{ + std::fill_n(&m_regdata[0], REGISTERS, 0); + m_regdata[0x02] = 0x20; + m_regdata[0xf8] = 0x1b; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void pcm_registers::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_regdata); +} + + +//------------------------------------------------- +// cache_channel_data - update the cache with +// data from the registers +//------------------------------------------------- + +void pcm_registers::cache_channel_data(uint32_t choffs, pcm_cache &cache) +{ + // compute step from octave and fnumber; the math here implies + // a .18 fraction but .16 should be perfectly fine + int32_t octave = int8_t(ch_octave(choffs) << 4) >> 4; + uint32_t fnum = ch_fnumber(choffs); + cache.step = ((0x400 | fnum) << (octave + 7)) >> 2; + + // total level is computed as a .10 value for interpolation + cache.total_level = ch_total_level(choffs) << 10; + + // compute panning values in terms of envelope attenuation + int32_t panpot = int8_t(ch_panpot(choffs) << 4) >> 4; + if (panpot >= 0) + { + cache.pan_left = (panpot == 7) ? 0x3ff : 0x20 * panpot; + cache.pan_right = 0; + } + else if (panpot >= -7) + { + cache.pan_left = 0; + cache.pan_right = (panpot == -7) ? 0x3ff : -0x20 * panpot; + } + else + cache.pan_left = cache.pan_right = 0x3ff; + + // determine the LFO stepping value; this how much to add to a running + // x.18 value for the LFO; steps were derived from frequencies in the + // manual and come out very close with these values + static const uint8_t s_lfo_steps[8] = { 1, 12, 19, 25, 31, 35, 37, 42 }; + cache.lfo_step = s_lfo_steps[ch_lfo_speed(choffs)]; + + // AM LFO depth values, derived from the manual; note each has at most + // 2 bits to make the "multiply" easy in hardware + static const uint8_t s_am_depth[8] = { 0, 0x14, 0x20, 0x28, 0x30, 0x40, 0x50, 0x80 }; + cache.am_depth = s_am_depth[ch_am_depth(choffs)]; + + // PM LFO depth values; these are converted from the manual's cents values + // into f-numbers; the computations come out quite cleanly so pretty sure + // these are correct + static const uint8_t s_pm_depth[8] = { 0, 2, 3, 4, 6, 12, 24, 48 }; + cache.pm_depth = s_pm_depth[ch_vibrato(choffs)]; + + // 4-bit sustain level, but 15 means 31 so effectively 5 bits + cache.eg_sustain = ch_sustain_level(choffs); + cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10; + cache.eg_sustain <<= 5; + + // compute the key scaling correction factor; 15 means don't do any correction + int32_t correction = ch_rate_correction(choffs); + if (correction == 15) + correction = 0; + else + correction = (octave + correction) * 2 + bitfield(fnum, 9); + + // compute the envelope generator rates + cache.eg_rate[EG_ATTACK] = effective_rate(ch_attack_rate(choffs), correction); + cache.eg_rate[EG_DECAY] = effective_rate(ch_decay_rate(choffs), correction); + cache.eg_rate[EG_SUSTAIN] = effective_rate(ch_sustain_rate(choffs), correction); + cache.eg_rate[EG_RELEASE] = effective_rate(ch_release_rate(choffs), correction); + cache.eg_rate[EG_REVERB] = 5; + + // if damping is on, override some things; essentially decay at a hardcoded + // rate of 48 until -12db (0x80), then at maximum rate for the rest + if (ch_damp(choffs) != 0) + { + cache.eg_rate[EG_DECAY] = 48; + cache.eg_rate[EG_SUSTAIN] = 63; + cache.eg_rate[EG_RELEASE] = 63; + cache.eg_sustain = 0x80; + } +} + + +//------------------------------------------------- +// effective_rate - return the effective rate, +// clamping and applying corrections as needed +//------------------------------------------------- + +uint32_t pcm_registers::effective_rate(uint32_t raw, uint32_t correction) +{ + // raw rates of 0 and 15 just pin to min/max + if (raw == 0) + return 0; + if (raw == 15) + return 63; + + // otherwise add the correction and clamp to range + return clamp(raw * 4 + correction, 0, 63); +} + + + +//********************************************************* +// PCM CHANNEL +//********************************************************* + +//------------------------------------------------- +// pcm_channel - constructor +//------------------------------------------------- + +pcm_channel::pcm_channel(pcm_engine &owner, uint32_t choffs) : + m_choffs(choffs), + m_baseaddr(0), + m_endpos(0), + m_looppos(0), + m_curpos(0), + m_nextpos(0), + m_lfo_counter(0), + m_eg_state(EG_RELEASE), + m_env_attenuation(0x3ff), + m_total_level(0x7f << 10), + m_format(0), + m_key_state(0), + m_regs(owner.regs()), + m_owner(owner) +{ +} + + +//------------------------------------------------- +// reset - reset the channel state +//------------------------------------------------- + +void pcm_channel::reset() +{ + m_baseaddr = 0; + m_endpos = 0; + m_looppos = 0; + m_curpos = 0; + m_nextpos = 0; + m_lfo_counter = 0; + m_eg_state = EG_RELEASE; + m_env_attenuation = 0x3ff; + m_total_level = 0x7f << 10; + m_format = 0; + m_key_state = 0; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void pcm_channel::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_baseaddr); + state.save_restore(m_endpos); + state.save_restore(m_looppos); + state.save_restore(m_curpos); + state.save_restore(m_nextpos); + state.save_restore(m_lfo_counter); + state.save_restore(m_eg_state); + state.save_restore(m_env_attenuation); + state.save_restore(m_total_level); + state.save_restore(m_format); + state.save_restore(m_key_state); +} + + +//------------------------------------------------- +// prepare - prepare for clocking +//------------------------------------------------- + +bool pcm_channel::prepare() +{ + // cache the data + m_regs.cache_channel_data(m_choffs, m_cache); + + // clock the key state + if ((m_key_state & KEY_PENDING) != 0) + { + uint8_t oldstate = m_key_state; + m_key_state = (m_key_state >> 1) & KEY_ON; + if (((oldstate ^ m_key_state) & KEY_ON) != 0) + { + if ((m_key_state & KEY_ON) != 0) + start_attack(); + else + start_release(); + } + } + + // set the total level directly if not interpolating + if (m_regs.ch_level_direct(m_choffs)) + m_total_level = m_cache.total_level; + + // we're active until we're quiet after the release + return (m_eg_state < EG_RELEASE || m_env_attenuation < EG_QUIET); +} + + +//------------------------------------------------- +// clock - master clocking function +//------------------------------------------------- + +void pcm_channel::clock(uint32_t env_counter) +{ + // clock the LFO, which is an x.18 value incremented based on the + // LFO speed value + m_lfo_counter += m_cache.lfo_step; + + // clock the envelope + clock_envelope(env_counter); + + // determine the step after applying vibrato + uint32_t step = m_cache.step; + if (m_cache.pm_depth != 0) + { + // shift the LFO by 1/4 cycle for PM so that it starts at 0 + uint32_t lfo_shifted = m_lfo_counter + (1 << 16); + int32_t lfo_value = bitfield(lfo_shifted, 10, 7); + if (bitfield(lfo_shifted, 17) != 0) + lfo_value ^= 0x7f; + lfo_value -= 0x40; + step += (lfo_value * int32_t(m_cache.pm_depth)) >> 7; + } + + // advance the sample step and loop as needed + m_curpos = m_nextpos; + m_nextpos = m_curpos + step; + if (m_nextpos >= m_endpos) + m_nextpos += m_looppos - m_endpos; + + // interpolate total level if needed + if (m_total_level != m_cache.total_level) + { + // max->min volume takes 156.4ms, or pretty close to 19/1024 per 44.1kHz sample + // min->max volume is half that, so advance by 38/1024 per sample + if (m_total_level < m_cache.total_level) + m_total_level = std::min(m_total_level + 19, m_cache.total_level); + else + m_total_level = std::max(m_total_level - 38, m_cache.total_level); + } +} + + +//------------------------------------------------- +// output - return the computed output value, with +// panning applied +//------------------------------------------------- + +void pcm_channel::output(output_data &output) const +{ + // early out if the envelope is effectively off + uint32_t envelope = m_env_attenuation; + if (envelope > EG_QUIET) + return; + + // add in LFO AM modulation + if (m_cache.am_depth != 0) + { + uint32_t lfo_value = bitfield(m_lfo_counter, 10, 7); + if (bitfield(m_lfo_counter, 17) != 0) + lfo_value ^= 0x7f; + envelope += (lfo_value * m_cache.am_depth) >> 7; + } + + // add in the current interpolated total level value, which is a .10 + // value shifted left by 2 + envelope += m_total_level >> 8; + + // add in panning effect and clamp + uint32_t lenv = std::min(envelope + m_cache.pan_left, 0x3ff); + uint32_t renv = std::min(envelope + m_cache.pan_right, 0x3ff); + + // convert to volume as a .11 fraction + int32_t lvol = attenuation_to_volume(lenv << 2); + int32_t rvol = attenuation_to_volume(renv << 2); + + // fetch current sample and add + int16_t sample = fetch_sample(); + uint32_t outnum = m_regs.ch_output_channel(m_choffs) * 2; + output.data[outnum + 0] += (lvol * sample) >> 15; + output.data[outnum + 1] += (rvol * sample) >> 15; +} + + +//------------------------------------------------- +// keyonoff - signal key on/off +//------------------------------------------------- + +void pcm_channel::keyonoff(bool on) +{ + // mark the key state as pending + m_key_state |= KEY_PENDING | (on ? KEY_PENDING_ON : 0); + + // don't log masked channels + if ((m_key_state & (KEY_PENDING_ON | KEY_ON)) == KEY_PENDING_ON && ((debug::GLOBAL_PCM_CHANNEL_MASK >> m_choffs) & 1) != 0) + { + debug::log_keyon("KeyOn PCM-%02d: num=%3d oct=%2d fnum=%03X level=%02X%c ADSR=%X/%X/%X/%X SL=%X", + m_choffs, + m_regs.ch_wave_table_num(m_choffs), + int8_t(m_regs.ch_octave(m_choffs) << 4) >> 4, + m_regs.ch_fnumber(m_choffs), + m_regs.ch_total_level(m_choffs), + m_regs.ch_level_direct(m_choffs) ? '!' : '/', + m_regs.ch_attack_rate(m_choffs), + m_regs.ch_decay_rate(m_choffs), + m_regs.ch_sustain_rate(m_choffs), + m_regs.ch_release_rate(m_choffs), + m_regs.ch_sustain_level(m_choffs)); + + if (m_regs.ch_rate_correction(m_choffs) != 15) + debug::log_keyon(" RC=%X", m_regs.ch_rate_correction(m_choffs)); + + if (m_regs.ch_pseudo_reverb(m_choffs) != 0) + debug::log_keyon(" %s", "REV"); + if (m_regs.ch_damp(m_choffs) != 0) + debug::log_keyon(" %s", "DAMP"); + + if (m_regs.ch_vibrato(m_choffs) != 0 || m_regs.ch_am_depth(m_choffs) != 0) + { + if (m_regs.ch_vibrato(m_choffs) != 0) + debug::log_keyon(" VIB=%d", m_regs.ch_vibrato(m_choffs)); + if (m_regs.ch_am_depth(m_choffs) != 0) + debug::log_keyon(" AM=%d", m_regs.ch_am_depth(m_choffs)); + debug::log_keyon(" LFO=%d", m_regs.ch_lfo_speed(m_choffs)); + } + debug::log_keyon("%s", "\n"); + } +} + + +//------------------------------------------------- +// load_wavetable - load a wavetable by fetching +// its data from external memory +//------------------------------------------------- + +void pcm_channel::load_wavetable() +{ + // determine the address of the wave table header + uint32_t wavnum = m_regs.ch_wave_table_num(m_choffs); + uint32_t wavheader = 12 * wavnum; + + // above 384 it may be in a different bank + if (wavnum >= 384) + { + uint32_t bank = m_regs.wave_table_header(); + if (bank != 0) + wavheader = 512*1024 * bank + (wavnum - 384) * 12; + } + + // fetch the 22-bit base address and 2-bit format + uint8_t byte = read_pcm(wavheader + 0); + m_format = bitfield(byte, 6, 2); + m_baseaddr = bitfield(byte, 0, 6) << 16; + m_baseaddr |= read_pcm(wavheader + 1) << 8; + m_baseaddr |= read_pcm(wavheader + 2) << 0; + + // fetch the 16-bit loop position + m_looppos = read_pcm(wavheader + 3) << 8; + m_looppos |= read_pcm(wavheader + 4); + m_looppos <<= 16; + + // fetch the 16-bit end position, which is stored as a negative value + // for some reason that is unclear + m_endpos = read_pcm(wavheader + 5) << 8; + m_endpos |= read_pcm(wavheader + 6); + m_endpos = -int32_t(m_endpos) << 16; + + // remaining data values set registers + m_owner.write(0x80 + m_choffs, read_pcm(wavheader + 7)); + m_owner.write(0x98 + m_choffs, read_pcm(wavheader + 8)); + m_owner.write(0xb0 + m_choffs, read_pcm(wavheader + 9)); + m_owner.write(0xc8 + m_choffs, read_pcm(wavheader + 10)); + m_owner.write(0xe0 + m_choffs, read_pcm(wavheader + 11)); + + // reset the envelope so we don't continue playing mid-sample from previous key ons + m_env_attenuation = 0x3ff; +} + + +//------------------------------------------------- +// read_pcm - read a byte from the external PCM +// memory interface +//------------------------------------------------- + +uint8_t pcm_channel::read_pcm(uint32_t address) const +{ + return m_owner.intf().ymfm_external_read(ACCESS_PCM, address); +} + + +//------------------------------------------------- +// start_attack - start the attack phase +//------------------------------------------------- + +void pcm_channel::start_attack() +{ + // don't change anything if already in attack state + if (m_eg_state == EG_ATTACK) + return; + m_eg_state = EG_ATTACK; + + // reset the LFO if requested + if (m_regs.ch_lfo_reset(m_choffs)) + m_lfo_counter = 0; + + // if the attack rate == 63 then immediately go to max attenuation + if (m_cache.eg_rate[EG_ATTACK] == 63) + m_env_attenuation = 0; + + // reset the positions + m_curpos = m_nextpos = 0; +} + + +//------------------------------------------------- +// start_release - start the release phase +//------------------------------------------------- + +void pcm_channel::start_release() +{ + // don't change anything if already in release or reverb state + if (m_eg_state >= EG_RELEASE) + return; + m_eg_state = EG_RELEASE; +} + + +//------------------------------------------------- +// clock_envelope - clock the envelope generator +//------------------------------------------------- + +void pcm_channel::clock_envelope(uint32_t env_counter) +{ + // handle attack->decay transitions + if (m_eg_state == EG_ATTACK && m_env_attenuation == 0) + m_eg_state = EG_DECAY; + + // handle decay->sustain transitions + if (m_eg_state == EG_DECAY && m_env_attenuation >= m_cache.eg_sustain) + m_eg_state = EG_SUSTAIN; + + // fetch the appropriate 6-bit rate value from the cache + uint32_t rate = m_cache.eg_rate[m_eg_state]; + + // compute the rate shift value; this is the shift needed to + // apply to the env_counter such that it becomes a 5.11 fixed + // point number + uint32_t rate_shift = rate >> 2; + env_counter <<= rate_shift; + + // see if the fractional part is 0; if not, it's not time to clock + if (bitfield(env_counter, 0, 11) != 0) + return; + + // determine the increment based on the non-fractional part of env_counter + uint32_t relevant_bits = bitfield(env_counter, (rate_shift <= 11) ? 11 : rate_shift, 3); + uint32_t increment = attenuation_increment(rate, relevant_bits); + + // attack is the only one that increases + if (m_eg_state == EG_ATTACK) + m_env_attenuation += (~m_env_attenuation * increment) >> 4; + + // all other cases are similar + else + { + // apply the increment + m_env_attenuation += increment; + + // clamp the final attenuation + if (m_env_attenuation >= 0x400) + m_env_attenuation = 0x3ff; + + // transition to reverb at -18dB if enabled + if (m_env_attenuation >= 0xc0 && m_eg_state < EG_REVERB && m_regs.ch_pseudo_reverb(m_choffs)) + m_eg_state = EG_REVERB; + } +} + + +//------------------------------------------------- +// fetch_sample - fetch a sample at the current +// position +//------------------------------------------------- + +int16_t pcm_channel::fetch_sample() const +{ + uint32_t addr = m_baseaddr; + uint32_t pos = m_curpos >> 16; + + // 8-bit PCM: shift up by 8 + if (m_format == 0) + return read_pcm(addr + pos) << 8; + + // 16-bit PCM: assemble from 2 halves + if (m_format == 2) + { + addr += pos * 2; + return (read_pcm(addr) << 8) | read_pcm(addr + 1); + } + + // 12-bit PCM: assemble out of half of 3 bytes + addr += (pos / 2) * 3; + if ((pos & 1) == 0) + return (read_pcm(addr + 0) << 8) | ((read_pcm(addr + 1) << 4) & 0xf0); + else + return (read_pcm(addr + 2) << 8) | ((read_pcm(addr + 1) << 0) & 0xf0); +} + + + +//********************************************************* +// PCM ENGINE +//********************************************************* + +//------------------------------------------------- +// pcm_engine - constructor +//------------------------------------------------- + +pcm_engine::pcm_engine(ymfm_interface &intf) : + m_intf(intf), + m_env_counter(0), + m_modified_channels(ALL_CHANNELS), + m_active_channels(ALL_CHANNELS) +{ + // create the channels + for (int chnum = 0; chnum < CHANNELS; chnum++) + m_channel[chnum] = std::make_unique(*this, chnum); +} + + +//------------------------------------------------- +// reset - reset the engine state +//------------------------------------------------- + +void pcm_engine::reset() +{ + // reset register state + m_regs.reset(); + + // reset each channel + for (auto &chan : m_channel) + chan->reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void pcm_engine::save_restore(ymfm_saved_state &state) +{ + // save our data + state.save_restore(m_env_counter); + + // save channel state + for (int chnum = 0; chnum < CHANNELS; chnum++) + m_channel[chnum]->save_restore(state); +} + + +//------------------------------------------------- +// clock - master clocking function +//------------------------------------------------- + +void pcm_engine::clock(uint32_t chanmask) +{ + // if something was modified, prepare + // also prepare every 4k samples to catch ending notes + if (m_modified_channels != 0 || m_prepare_count++ >= 4096) + { + // call each channel to prepare + m_active_channels = 0; + for (int chnum = 0; chnum < CHANNELS; chnum++) + if (bitfield(chanmask, chnum)) + if (m_channel[chnum]->prepare()) + m_active_channels |= 1 << chnum; + + // reset the modified channels and prepare count + m_modified_channels = m_prepare_count = 0; + } + + // increment the envelope counter; the envelope generator + // only clocks every other sample in order to make the PCM + // envelopes line up with the FM envelopes (after taking into + // account the different FM sampling rate) + m_env_counter++; + + // now update the state of all the channels and operators + for (int chnum = 0; chnum < CHANNELS; chnum++) + if (bitfield(chanmask, chnum)) + m_channel[chnum]->clock(m_env_counter >> 1); +} + + +//------------------------------------------------- +// update - master update function +//------------------------------------------------- + +void pcm_engine::output(output_data &output, uint32_t chanmask) +{ + // mask out some channels for debug purposes + chanmask &= debug::GLOBAL_PCM_CHANNEL_MASK; + + // compute the output of each channel + for (int chnum = 0; chnum < CHANNELS; chnum++) + if (bitfield(chanmask, chnum)) + m_channel[chnum]->output(output); +} + + +//------------------------------------------------- +// read - handle reads from the PCM registers +//------------------------------------------------- + +uint8_t pcm_engine::read(uint32_t regnum) +{ + // handle reads from the data register + if (regnum == 0x06 && m_regs.memory_access_mode() != 0) + return m_intf.ymfm_external_read(ACCESS_PCM, m_regs.memory_address_autoinc()); + + return m_regs.read(regnum); +} + + +//------------------------------------------------- +// write - handle writes to the PCM registers +//------------------------------------------------- + +void pcm_engine::write(uint32_t regnum, uint8_t data) +{ + // handle reads to the data register + if (regnum == 0x06 && m_regs.memory_access_mode() != 0) + { + m_intf.ymfm_external_write(ACCESS_PCM, m_regs.memory_address_autoinc(), data); + return; + } + + // for now just mark all channels as modified + m_modified_channels = ALL_CHANNELS; + + // most writes are passive, consumed only when needed + m_regs.write(regnum, data); + + // however, process keyons immediately + if (regnum >= 0x68 && regnum <= 0x7f) + m_channel[regnum - 0x68]->keyonoff(bitfield(data, 7)); + + // and also wavetable writes + else if (regnum >= 0x08 && regnum <= 0x1f) + m_channel[regnum - 0x08]->load_wavetable(); +} + +} diff --git a/src/sound/ymfm/ymfm_pcm.h b/src/sound/ymfm/ymfm_pcm.h new file mode 100644 index 000000000..2022a69b9 --- /dev/null +++ b/src/sound/ymfm/ymfm_pcm.h @@ -0,0 +1,347 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_PCM_H +#define YMFM_PCM_H + +#pragma once + +#include "ymfm.h" + +namespace ymfm +{ + +/* +Note to self: Sega "Multi-PCM" is almost identical to this + +28 channels + +Writes: +00 = data reg, causes write +01 = target slot = data - (data / 8) +02 = address (clamped to 7) + +Slot data (registers with ADSR/KSR seem to be inaccessible): +0: xxxx---- panpot +1: xxxxxxxx wavetable low +2: xxxxxx-- pitch low + -------x wavetable high +3: xxxx---- octave + ----xxxx pitch hi +4: x------- key on +5: xxxxxxx- total level + -------x level direct (0=interpolate) +6: --xxx--- LFO frequency + -----xxx PM sensitivity +7: -----xxx AM sensitivity + +Sample data: ++00: start hi ++01: start mid ++02: start low ++03: loop hi ++04: loop low ++05: -end hi ++06: -end low ++07: vibrato (reg 6) ++08: attack/decay ++09: sustain level/rate ++0A: ksr/release ++0B: LFO amplitude (reg 7) + +*/ + +//********************************************************* +// INTERFACE CLASSES +//********************************************************* + +class pcm_engine; + + +// ======================> pcm_cache + +// this class holds data that is computed once at the start of clocking +// and remains static during subsequent sound generation +struct pcm_cache +{ + uint32_t step; // sample position step, as a .16 value + uint32_t total_level; // target total level, as a .10 value + uint32_t pan_left; // left panning attenuation + uint32_t pan_right; // right panning attenuation + uint32_t eg_sustain; // sustain level, shifted up to envelope values + uint8_t eg_rate[EG_STATES]; // envelope rate, including KSR + uint8_t lfo_step; // stepping value for LFO + uint8_t am_depth; // scale value for AM LFO + uint8_t pm_depth; // scale value for PM LFO +}; + + +// ======================> pcm_registers + +// +// PCM register map: +// +// System-wide registers: +// 00-01 xxxxxxxx LSI Test +// 02 -------x Memory access mode (0=sound gen, 1=read/write) +// ------x- Memory type (0=ROM, 1=ROM+SRAM) +// ---xxx-- Wave table header +// xxx----- Device ID (=1 for YMF278B) +// 03 --xxxxxx Memory address high +// 04 xxxxxxxx Memory address mid +// 05 xxxxxxxx Memory address low +// 06 xxxxxxxx Memory data +// F8 --xxx--- Mix control (FM_R) +// -----xxx Mix control (FM_L) +// F9 --xxx--- Mix control (PCM_R) +// -----xxx Mix control (PCM_L) +// +// Channel-specific registers: +// 08-1F xxxxxxxx Wave table number low +// 20-37 -------x Wave table number high +// xxxxxxx- F-number low +// 38-4F -----xxx F-number high +// ----x--- Pseudo-reverb +// xxxx---- Octave +// 50-67 xxxxxxx- Total level +// -------x Level direct +// 68-7F x------- Key on +// -x------ Damp +// --x----- LFO reset +// ---x---- Output channel +// ----xxxx Panpot +// 80-97 --xxx--- LFO speed +// -----xxx Vibrato +// 98-AF xxxx---- Attack rate +// ----xxxx Decay rate +// B0-C7 xxxx---- Sustain level +// ----xxxx Sustain rate +// C8-DF xxxx---- Rate correction +// ----xxxx Release rate +// E0-F7 -----xxx AM depth + +class pcm_registers +{ +public: + // constants + static constexpr uint32_t OUTPUTS = 4; + static constexpr uint32_t CHANNELS = 24; + static constexpr uint32_t REGISTERS = 0x100; + static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1; + + // constructor + pcm_registers() { } + + // save/restore + void save_restore(ymfm_saved_state &state); + + // reset to initial state + void reset(); + + // update cache information + void cache_channel_data(uint32_t choffs, pcm_cache &cache); + + // direct read/write access + uint8_t read(uint32_t index ) { return m_regdata[index]; } + void write(uint32_t index, uint8_t data) { m_regdata[index] = data; } + + // system-wide registers + uint32_t memory_access_mode() const { return bitfield(m_regdata[0x02], 0); } + uint32_t memory_type() const { return bitfield(m_regdata[0x02], 1); } + uint32_t wave_table_header() const { return bitfield(m_regdata[0x02], 2, 3); } + uint32_t device_id() const { return bitfield(m_regdata[0x02], 5, 3); } + uint32_t memory_address() const { return (bitfield(m_regdata[0x03], 0, 6) << 16) | (m_regdata[0x04] << 8) | m_regdata[0x05]; } + uint32_t memory_data() const { return m_regdata[0x06]; } + uint32_t mix_fm_r() const { return bitfield(m_regdata[0xf8], 3, 3); } + uint32_t mix_fm_l() const { return bitfield(m_regdata[0xf8], 0, 3); } + uint32_t mix_pcm_r() const { return bitfield(m_regdata[0xf9], 3, 3); } + uint32_t mix_pcm_l() const { return bitfield(m_regdata[0xf9], 0, 3); } + + // per-channel registers + uint32_t ch_wave_table_num(uint32_t choffs) const { return m_regdata[choffs + 0x08] | (bitfield(m_regdata[choffs + 0x20], 0) << 8); } + uint32_t ch_fnumber(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x20], 1, 7) | (bitfield(m_regdata[choffs + 0x38], 0, 3) << 7); } + uint32_t ch_pseudo_reverb(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x38], 3); } + uint32_t ch_octave(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x38], 4, 4); } + uint32_t ch_total_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x50], 1, 7); } + uint32_t ch_level_direct(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x50], 0); } + uint32_t ch_keyon(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 7); } + uint32_t ch_damp(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 6); } + uint32_t ch_lfo_reset(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 5); } + uint32_t ch_output_channel(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 4); } + uint32_t ch_panpot(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 0, 4); } + uint32_t ch_lfo_speed(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x80], 3, 3); } + uint32_t ch_vibrato(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x80], 0, 3); } + uint32_t ch_attack_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x98], 4, 4); } + uint32_t ch_decay_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x98], 0, 4); } + uint32_t ch_sustain_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xb0], 4, 4); } + uint32_t ch_sustain_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xb0], 0, 4); } + uint32_t ch_rate_correction(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xc8], 4, 4); } + uint32_t ch_release_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xc8], 0, 4); } + uint32_t ch_am_depth(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xe0], 0, 3); } + + // return the memory address and increment it + uint32_t memory_address_autoinc() + { + uint32_t result = memory_address(); + uint32_t newval = result + 1; + m_regdata[0x05] = newval >> 0; + m_regdata[0x06] = newval >> 8; + m_regdata[0x07] = (newval >> 16) & 0x3f; + return result; + } + +private: + // internal helpers + uint32_t effective_rate(uint32_t raw, uint32_t correction); + + // internal state + uint8_t m_regdata[REGISTERS]; // register data +}; + + +// ======================> pcm_channel + +class pcm_channel +{ + static constexpr uint8_t KEY_ON = 0x01; + static constexpr uint8_t KEY_PENDING_ON = 0x02; + static constexpr uint8_t KEY_PENDING = 0x04; + + // "quiet" value, used to optimize when we can skip doing working + static constexpr uint32_t EG_QUIET = 0x200; + +public: + using output_data = ymfm_output; + + // constructor + pcm_channel(pcm_engine &owner, uint32_t choffs); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // reset the channel state + void reset(); + + // return the channel offset + uint32_t choffs() const { return m_choffs; } + + // prepare prior to clocking + bool prepare(); + + // master clocking function + void clock(uint32_t env_counter); + + // return the computed output value, with panning applied + void output(output_data &output) const; + + // signal key on/off + void keyonoff(bool on); + + // load a new wavetable entry + void load_wavetable(); + +private: + // internal helpers + void start_attack(); + void start_release(); + void clock_envelope(uint32_t env_counter); + int16_t fetch_sample() const; + uint8_t read_pcm(uint32_t address) const; + + // internal state + uint32_t const m_choffs; // channel offset + uint32_t m_baseaddr; // base address + uint32_t m_endpos; // ending position + uint32_t m_looppos; // loop position + uint32_t m_curpos; // current position + uint32_t m_nextpos; // next position + uint32_t m_lfo_counter; // LFO counter + envelope_state m_eg_state; // envelope state + uint16_t m_env_attenuation; // computed envelope attenuation + uint32_t m_total_level; // total level with as 7.10 for interp + uint8_t m_format; // sample format + uint8_t m_key_state; // current key state + pcm_cache m_cache; // cached data + pcm_registers &m_regs; // reference to registers + pcm_engine &m_owner; // reference to our owner +}; + + +// ======================> pcm_engine + +class pcm_engine +{ +public: + static constexpr int OUTPUTS = pcm_registers::OUTPUTS; + static constexpr int CHANNELS = pcm_registers::CHANNELS; + static constexpr uint32_t ALL_CHANNELS = pcm_registers::ALL_CHANNELS; + using output_data = pcm_channel::output_data; + + // constructor + pcm_engine(ymfm_interface &intf); + + // reset our status + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // master clocking function + void clock(uint32_t chanmask); + + // compute sum of channel outputs + void output(output_data &output, uint32_t chanmask); + + // read from the PCM registers + uint8_t read(uint32_t regnum); + + // write to the PCM registers + void write(uint32_t regnum, uint8_t data); + + // return a reference to our interface + ymfm_interface &intf() { return m_intf; } + + // return a reference to our registers + pcm_registers ®s() { return m_regs; } + +private: + // internal state + ymfm_interface &m_intf; // reference to the interface + uint32_t m_env_counter; // envelope counter + uint32_t m_modified_channels; // bitmask of modified channels + uint32_t m_active_channels; // bitmask of active channels + uint32_t m_prepare_count; // counter to do periodic prepare sweeps + std::unique_ptr m_channel[CHANNELS]; // array of channels + pcm_registers m_regs; // registers +}; + +} + +#endif // YMFM_PCM_H diff --git a/src/sound/ymfm/ymfm_ssg.cpp b/src/sound/ymfm/ymfm_ssg.cpp new file mode 100644 index 000000000..1c477d0de --- /dev/null +++ b/src/sound/ymfm/ymfm_ssg.cpp @@ -0,0 +1,279 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "ymfm_ssg.h" + +namespace ymfm +{ + +//********************************************************* +// SSG REGISTERS +//********************************************************* + +//------------------------------------------------- +// reset - reset the register state +//------------------------------------------------- + +void ssg_registers::reset() +{ + std::fill_n(&m_regdata[0], REGISTERS, 0); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ssg_registers::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_regdata); +} + + + +//********************************************************* +// SSG ENGINE +//********************************************************* + +//------------------------------------------------- +// ssg_engine - constructor +//------------------------------------------------- + +ssg_engine::ssg_engine(ymfm_interface &intf) : + m_intf(intf), + m_tone_count{ 0,0,0 }, + m_tone_state{ 0,0,0 }, + m_envelope_count(0), + m_envelope_state(0), + m_noise_count(0), + m_noise_state(1), + m_override(nullptr) +{ +} + + +//------------------------------------------------- +// reset - reset the engine state +//------------------------------------------------- + +void ssg_engine::reset() +{ + // defer to the override if present + if (m_override != nullptr) + return m_override->ssg_reset(); + + // reset register state + m_regs.reset(); + + // reset engine state + for (int chan = 0; chan < 3; chan++) + { + m_tone_count[chan] = 0; + m_tone_state[chan] = 0; + } + m_envelope_count = 0; + m_envelope_state = 0; + m_noise_count = 0; + m_noise_state = 1; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ssg_engine::save_restore(ymfm_saved_state &state) +{ + // save register state + m_regs.save_restore(state); + + // save engine state + state.save_restore(m_tone_count); + state.save_restore(m_tone_state); + state.save_restore(m_envelope_count); + state.save_restore(m_envelope_state); + state.save_restore(m_noise_count); + state.save_restore(m_noise_state); +} + + +//------------------------------------------------- +// clock - master clocking function +//------------------------------------------------- + +void ssg_engine::clock() +{ + // clock tones; tone period units are clock/16 but since we run at clock/8 + // that works out for us to toggle the state (50% duty cycle) at twice the + // programmed period + for (int chan = 0; chan < 3; chan++) + { + m_tone_count[chan]++; + if (m_tone_count[chan] >= m_regs.ch_tone_period(chan)) + { + m_tone_state[chan] ^= 1; + m_tone_count[chan] = 0; + } + } + + // clock noise; noise period units are clock/16 but since we run at clock/8, + // our counter needs a right shift prior to compare; note that a period of 0 + // should produce an indentical result to a period of 1, so add a special + // check against that case + m_noise_count++; + if ((m_noise_count >> 1) >= m_regs.noise_period() && m_noise_count != 1) + { + m_noise_state ^= (bitfield(m_noise_state, 0) ^ bitfield(m_noise_state, 3)) << 17; + m_noise_state >>= 1; + m_noise_count = 0; + } + + // clock envelope; envelope period units are clock/8 (manual says clock/256 + // but that's for all 32 steps) + m_envelope_count++; + if (m_envelope_count >= m_regs.envelope_period()) + { + m_envelope_state++; + m_envelope_count = 0; + } +} + + +//------------------------------------------------- +// output - output the current state +//------------------------------------------------- + +void ssg_engine::output(output_data &output) +{ + // volume to amplitude table, taken from MAME's implementation but biased + // so that 0 == 0 + static int16_t const s_amplitudes[32] = + { + 0, 32, 78, 141, 178, 222, 262, 306, + 369, 441, 509, 585, 701, 836, 965, 1112, + 1334, 1595, 1853, 2146, 2576, 3081, 3576, 4135, + 5000, 6006, 7023, 8155, 9963,11976,14132,16382 + }; + + // compute the envelope volume + uint32_t envelope_volume; + if ((m_regs.envelope_hold() | (m_regs.envelope_continue() ^ 1)) && m_envelope_state >= 32) + { + m_envelope_state = 32; + envelope_volume = ((m_regs.envelope_attack() ^ m_regs.envelope_alternate()) & m_regs.envelope_continue()) ? 31 : 0; + } + else + { + uint32_t attack = m_regs.envelope_attack(); + if (m_regs.envelope_alternate()) + attack ^= bitfield(m_envelope_state, 5); + envelope_volume = (m_envelope_state & 31) ^ (attack ? 0 : 31); + } + + // iterate over channels + for (int chan = 0; chan < 3; chan++) + { + // noise depends on the noise state, which is the LSB of m_noise_state + uint32_t noise_on = m_regs.ch_noise_enable_n(chan) | m_noise_state; + + // tone depends on the current tone state + uint32_t tone_on = m_regs.ch_tone_enable_n(chan) | m_tone_state[chan]; + + // if neither tone nor noise enabled, return 0 + uint32_t volume; + if ((noise_on & tone_on) == 0) + volume = 0; + + // if the envelope is enabled, use its amplitude + else if (m_regs.ch_envelope_enable(chan)) + volume = envelope_volume; + + // otherwise, scale the tone amplitude up to match envelope values + // according to the datasheet, amplitude 15 maps to envelope 31 + else + { + volume = m_regs.ch_amplitude(chan) * 2; + if (volume != 0) + volume |= 1; + } + + // convert to amplitude + output.data[chan] = s_amplitudes[volume]; + } +} + + +//------------------------------------------------- +// read - handle reads from the SSG registers +//------------------------------------------------- + +uint8_t ssg_engine::read(uint32_t regnum) +{ + // defer to the override if present + if (m_override != nullptr) + return m_override->ssg_read(regnum); + + // read from the I/O ports call the handlers if they are configured for input + if (regnum == 0x0e && !m_regs.io_a_out()) + return m_intf.ymfm_external_read(ACCESS_IO, 0); + else if (regnum == 0x0f && !m_regs.io_b_out()) + return m_intf.ymfm_external_read(ACCESS_IO, 1); + + // otherwise just return the register value + return m_regs.read(regnum); +} + + +//------------------------------------------------- +// write - handle writes to the SSG registers +//------------------------------------------------- + +void ssg_engine::write(uint32_t regnum, uint8_t data) +{ + // defer to the override if present + if (m_override != nullptr) + return m_override->ssg_write(regnum, data); + + // store the raw value to the register array; + // most writes are passive, consumed only when needed + m_regs.write(regnum, data); + + // writes to the envelope shape register reset the state + if (regnum == 0x0d) + m_envelope_state = 0; + + // writes to the I/O ports call the handlers if they are configured for output + else if (regnum == 0x0e && m_regs.io_a_out()) + m_intf.ymfm_external_write(ACCESS_IO, 0, data); + else if (regnum == 0x0f && m_regs.io_b_out()) + m_intf.ymfm_external_write(ACCESS_IO, 1, data); +} + +} diff --git a/src/sound/ymfm/ymfm_ssg.h b/src/sound/ymfm/ymfm_ssg.h new file mode 100644 index 000000000..749ad146f --- /dev/null +++ b/src/sound/ymfm/ymfm_ssg.h @@ -0,0 +1,205 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_SSG_H +#define YMFM_SSG_H + +#pragma once + +#include "ymfm.h" + +namespace ymfm +{ + +//********************************************************* +// OVERRIDE INTERFACE +//********************************************************* + +// ======================> ssg_override + +// this class describes a simple interface to allow the internal SSG to be +// overridden with another implementation +class ssg_override +{ +public: + // reset our status + virtual void ssg_reset() = 0; + + // read/write to the SSG registers + virtual uint8_t ssg_read(uint32_t regnum) = 0; + virtual void ssg_write(uint32_t regnum, uint8_t data) = 0; + + // notification when the prescale has changed + virtual void ssg_prescale_changed() = 0; +}; + + +//********************************************************* +// REGISTER CLASS +//********************************************************* + +// ======================> ssg_registers + +// +// SSG register map: +// +// System-wide registers: +// 06 ---xxxxx Noise period +// 07 x------- I/O B in(0) or out(1) +// -x------ I/O A in(0) or out(1) +// --x----- Noise enable(0) or disable(1) for channel C +// ---x---- Noise enable(0) or disable(1) for channel B +// ----x--- Noise enable(0) or disable(1) for channel A +// -----x-- Tone enable(0) or disable(1) for channel C +// ------x- Tone enable(0) or disable(1) for channel B +// -------x Tone enable(0) or disable(1) for channel A +// 0B xxxxxxxx Envelope period fine +// 0C xxxxxxxx Envelope period coarse +// 0D ----x--- Envelope shape: continue +// -----x-- Envelope shape: attack/decay +// ------x- Envelope shape: alternate +// -------x Envelope shape: hold +// 0E xxxxxxxx 8-bit parallel I/O port A +// 0F xxxxxxxx 8-bit parallel I/O port B +// +// Per-channel registers: +// 00,02,04 xxxxxxxx Tone period (fine) for channel A,B,C +// 01,03,05 ----xxxx Tone period (coarse) for channel A,B,C +// 08,09,0A ---x---- Mode: fixed(0) or variable(1) for channel A,B,C +// ----xxxx Amplitude for channel A,B,C +// +class ssg_registers +{ +public: + // constants + static constexpr uint32_t OUTPUTS = 3; + static constexpr uint32_t CHANNELS = 3; + static constexpr uint32_t REGISTERS = 0x10; + static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1; + + // constructor + ssg_registers() { } + + // reset to initial state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // direct read/write access + uint8_t read(uint32_t index) { return m_regdata[index]; } + void write(uint32_t index, uint8_t data) { m_regdata[index] = data; } + + // system-wide registers + uint32_t noise_period() const { return bitfield(m_regdata[0x06], 0, 5); } + uint32_t io_b_out() const { return bitfield(m_regdata[0x07], 7); } + uint32_t io_a_out() const { return bitfield(m_regdata[0x07], 6); } + uint32_t envelope_period() const { return m_regdata[0x0b] | (m_regdata[0x0c] << 8); } + uint32_t envelope_continue() const { return bitfield(m_regdata[0x0d], 3); } + uint32_t envelope_attack() const { return bitfield(m_regdata[0x0d], 2); } + uint32_t envelope_alternate() const { return bitfield(m_regdata[0x0d], 1); } + uint32_t envelope_hold() const { return bitfield(m_regdata[0x0d], 0); } + uint32_t io_a_data() const { return m_regdata[0x0e]; } + uint32_t io_b_data() const { return m_regdata[0x0f]; } + + // per-channel registers + uint32_t ch_noise_enable_n(uint32_t choffs) const { return bitfield(m_regdata[0x07], 3 + choffs); } + uint32_t ch_tone_enable_n(uint32_t choffs) const { return bitfield(m_regdata[0x07], 0 + choffs); } + uint32_t ch_tone_period(uint32_t choffs) const { return m_regdata[0x00 + 2 * choffs] | (bitfield(m_regdata[0x01 + 2 * choffs], 0, 4) << 8); } + uint32_t ch_envelope_enable(uint32_t choffs) const { return bitfield(m_regdata[0x08 + choffs], 4); } + uint32_t ch_amplitude(uint32_t choffs) const { return bitfield(m_regdata[0x08 + choffs], 0, 4); } + +private: + // internal state + uint8_t m_regdata[REGISTERS]; // register data +}; + + +// ======================> ssg_engine + +class ssg_engine +{ +public: + static constexpr int OUTPUTS = ssg_registers::OUTPUTS; + static constexpr int CHANNELS = ssg_registers::CHANNELS; + static constexpr int CLOCK_DIVIDER = 8; + + using output_data = ymfm_output; + + // constructor + ssg_engine(ymfm_interface &intf); + + // configure an override + void override(ssg_override &override) { m_override = &override; } + + // reset our status + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // master clocking function + void clock(); + + // compute sum of channel outputs + void output(output_data &output); + + // read/write to the SSG registers + uint8_t read(uint32_t regnum); + void write(uint32_t regnum, uint8_t data); + + // return a reference to our interface + ymfm_interface &intf() { return m_intf; } + + // return a reference to our registers + ssg_registers ®s() { return m_regs; } + + // true if we are overridden + bool overridden() const { return (m_override != nullptr); } + + // indicate the prescale has changed + void prescale_changed() { if (m_override != nullptr) m_override->ssg_prescale_changed(); } + +private: + // internal state + ymfm_interface &m_intf; // reference to the interface + uint32_t m_tone_count[3]; // current tone counter + uint32_t m_tone_state[3]; // current tone state + uint32_t m_envelope_count; // envelope counter + uint32_t m_envelope_state; // envelope state + uint32_t m_noise_count; // current noise counter + uint32_t m_noise_state; // current noise state + ssg_registers m_regs; // registers + ssg_override *m_override; // override interface +}; + +} + +#endif // YMFM_SSG_H diff --git a/src/video/vid_paradise.c b/src/video/vid_paradise.c index 59b9f2247..3ca44746c 100644 --- a/src/video/vid_paradise.c +++ b/src/video/vid_paradise.c @@ -77,6 +77,64 @@ static video_timings_t timing_paradise_wd90c = {VIDEO_ISA, 3, 3, 6, 5, 5, 1 void paradise_remap(paradise_t *paradise); +uint8_t paradise_in(uint16_t addr, void *p) +{ + paradise_t *paradise = (paradise_t *)p; + svga_t *svga = ¶dise->svga; + + if (((addr & 0xfff0) == 0x3d0 || (addr & 0xfff0) == 0x3b0) && !(svga->miscout & 1)) + addr ^= 0x60; + + switch (addr) + { + case 0x3c5: + if (svga->seqaddr > 7) + { + if (paradise->type < WD90C11 || svga->seqregs[6] != 0x48) + return 0xff; + if (svga->seqaddr > 0x12) + return 0xff; + return svga->seqregs[svga->seqaddr & 0x1f]; + } + break; + + case 0x3c6: case 0x3c7: case 0x3c8: case 0x3c9: + if (paradise->type == WD90C30) + return sc1148x_ramdac_in(addr, 0, svga->ramdac, svga); + return svga_in(addr, svga); + + case 0x3cf: + if (svga->gdcaddr >= 9 && svga->gdcaddr <= 0x0e) { + if (svga->gdcreg[0x0f] & 0x10) + return 0xff; + } + switch (svga->gdcaddr) { + case 0x0b: + if (paradise->type == WD90C30) { + if (paradise->vram_mask == ((512 << 10) - 1)) { + svga->gdcreg[0x0b] |= 0xc0; + svga->gdcreg[0x0b] &= ~0x40; + } + } + return svga->gdcreg[0x0b]; + + case 0x0f: + return (svga->gdcreg[0x0f] & 0x17) | 0x80; + } + break; + + case 0x3D4: + return svga->crtcreg; + case 0x3D5: + if ((paradise->type == PVGA1A) && (svga->crtcreg & 0x20)) + return 0xff; + if (svga->crtcreg > 0x29 && svga->crtcreg < 0x30 && (svga->crtc[0x29] & 0x88) != 0x80) + return 0xff; + return svga->crtc[svga->crtcreg]; + } + return svga_in(addr, svga); +} + void paradise_out(uint16_t addr, uint8_t val, void *p) { paradise_t *paradise = (paradise_t *)p; @@ -187,69 +245,21 @@ void paradise_out(uint16_t addr, uint8_t val, void *p) } } break; + + case 0x46e8: + io_removehandler(0x03c0, 0x0020, paradise_in, NULL, NULL, paradise_out, NULL, NULL, paradise); + mem_mapping_disable(¶dise->svga.mapping); + if (val & 8) + { + io_sethandler(0x03c0, 0x0020, paradise_in, NULL, NULL, paradise_out, NULL, NULL, paradise); + mem_mapping_enable(¶dise->svga.mapping); + } + break; } svga_out(addr, val, svga); } -uint8_t paradise_in(uint16_t addr, void *p) -{ - paradise_t *paradise = (paradise_t *)p; - svga_t *svga = ¶dise->svga; - - if (((addr & 0xfff0) == 0x3d0 || (addr & 0xfff0) == 0x3b0) && !(svga->miscout & 1)) - addr ^= 0x60; - - switch (addr) - { - case 0x3c5: - if (svga->seqaddr > 7) - { - if (paradise->type < WD90C11 || svga->seqregs[6] != 0x48) - return 0xff; - if (svga->seqaddr > 0x12) - return 0xff; - return svga->seqregs[svga->seqaddr & 0x1f]; - } - break; - - case 0x3c6: case 0x3c7: case 0x3c8: case 0x3c9: - if (paradise->type == WD90C30) - return sc1148x_ramdac_in(addr, 0, svga->ramdac, svga); - return svga_in(addr, svga); - - case 0x3cf: - if (svga->gdcaddr >= 9 && svga->gdcaddr <= 0x0e) { - if (svga->gdcreg[0x0f] & 0x10) - return 0xff; - } - switch (svga->gdcaddr) { - case 0x0b: - if (paradise->type == WD90C30) { - if (paradise->vram_mask == ((512 << 10) - 1)) { - svga->gdcreg[0x0b] |= 0xc0; - svga->gdcreg[0x0b] &= ~0x40; - } - } - return svga->gdcreg[0x0b]; - - case 0x0f: - return (svga->gdcreg[0x0f] & 0x17) | 0x80; - } - break; - - case 0x3D4: - return svga->crtcreg; - case 0x3D5: - if ((paradise->type == PVGA1A) && (svga->crtcreg & 0x20)) - return 0xff; - if (svga->crtcreg > 0x29 && svga->crtcreg < 0x30 && (svga->crtc[0x29] & 0x88) != 0x80) - return 0xff; - return svga->crtc[svga->crtcreg]; - } - return svga_in(addr, svga); -} - void paradise_remap(paradise_t *paradise) { svga_t *svga = ¶dise->svga; @@ -579,6 +589,7 @@ void *paradise_init(const device_t *info, uint32_t memsize) case WD90C11: svga->crtc[0x36] = '1'; svga->crtc[0x37] = '1'; + io_sethandler(0x46e8, 0x0001, paradise_in, NULL, NULL, paradise_out, NULL, NULL, paradise); break; case WD90C30: svga->crtc[0x36] = '3'; diff --git a/src/win/Makefile.mingw b/src/win/Makefile.mingw index 5d3b34d95..8ec2ba105 100644 --- a/src/win/Makefile.mingw +++ b/src/win/Makefile.mingw @@ -241,7 +241,7 @@ VPATH := $(EXPATH) . $(CODEGEN) minitrace cpu \ sio sound \ sound/munt sound/munt/c_interface sound/munt/sha1 \ sound/munt/srchelper sound/munt/srchelper/srctools/src \ - sound/resid-fp \ + sound/resid-fp sound/ymfm \ scsi video network network/slirp win ifeq ($(X64), y) TOOL_PREFIX := x86_64-w64-mingw32- @@ -535,7 +535,7 @@ CXXFLAGS := $(CFLAGS) # Create the (final) list of objects to build. # ######################################################################### MAINOBJ := 86box.o config.o log.o random.o timer.o io.o acpi.o apm.o dma.o ddma.o \ - nmi.o pic.o pit.o port_6x.o port_92.o ppi.o pci.o mca.o fifo8.o \ + nmi.o pic.o pit.o pit_fast.o port_6x.o port_92.o ppi.o pci.o mca.o fifo8.o \ usb.o device.o nvr.o nvr_at.o nvr_ps2.o machine_status.o \ $(VNCOBJ) @@ -674,7 +674,9 @@ PRINTOBJ := png.o prt_cpmap.o \ prt_escp.o prt_text.o prt_ps.o SNDOBJ := sound.o \ - snd_opl.o snd_opl_nuked.o \ + snd_opl.o snd_opl_nuked.o snd_opl_ymfm.o \ + ymfm_adpcm.o ymfm_misc.o ymfm_opl.o ymfm_opm.o \ + ymfm_opn.o ymfm_opq.o ymfm_opz.o ymfm_pcm.o ymfm_ssg.o \ snd_resid.o \ convolve.o convolve-sse.o envelope.o extfilt.o \ filter.o pot.o sid.o voice.o wave6581__ST.o \ diff --git a/src/win/languages/pt-BR.rc b/src/win/languages/pt-BR.rc index 81b2e290a..5e23a9d71 100644 --- a/src/win/languages/pt-BR.rc +++ b/src/win/languages/pt-BR.rc @@ -80,7 +80,7 @@ BEGIN MENUITEM "Monitor VGA &invertido", IDM_VID_INVERT POPUP "&Tipo de tela VGA" BEGIN - MENUITEM "&Cor RGB", IDM_VID_GRAY_RGB + MENUITEM "&Cores RGB", IDM_VID_GRAY_RGB MENUITEM "Tons de cinza &RGB", IDM_VID_GRAY_MONO MENUITEM "Monitor &âmbar", IDM_VID_GRAY_AMBER MENUITEM "Monitor &verde", IDM_VID_GRAY_GREEN @@ -267,15 +267,15 @@ END #define STR_MB "MB" #define STR_MEMORY "Memória:" #define STR_TIME_SYNC "Sincronização da hora" -#define STR_DISABLED "Desativada" -#define STR_ENABLED_LOCAL "Ativada (hora local)" -#define STR_ENABLED_UTC "Ativada (UTC)" +#define STR_DISABLED "Desativar" +#define STR_ENABLED_LOCAL "Ativar (hora local)" +#define STR_ENABLED_UTC "Ativar (UTC)" #define STR_DYNAREC "Recompilador dinâmico" #define STR_VIDEO "Vídeo:" #define STR_VOODOO "3DFX Voodoo" -#define STR_IBM8514 "IBM 8514/a Graphics" -#define STR_XGA "XGA Graphics" +#define STR_IBM8514 "Gráficos IBM 8514/a" +#define STR_XGA "Gráficos XGA" #define STR_MOUSE "Mouse:" #define STR_JOYSTICK "Joystick:" @@ -340,7 +340,7 @@ END #define STR_SIZE_MB "Tamanho (MB):" #define STR_TYPE "Tipo:" #define STR_IMG_FORMAT "Formato:" -#define STR_BLOCK_SIZE "Bloco:" +#define STR_BLOCK_SIZE "Blocos:" #define STR_FLOPPY_DRIVES "Unidades de disquete:" #define STR_TURBO "Turbo" @@ -376,7 +376,7 @@ BEGIN 2048 "86Box" IDS_2049 "Erro" IDS_2050 "Erro fatal" - IDS_2051 " - PAUSED" + IDS_2051 " - PAUSADO" IDS_2052 "Use Ctrl+Alt+PgDn para retornar ao modo janela" IDS_2053 "Velocidade" IDS_2054 "ZIP %03i %i (%s): %ls" @@ -435,7 +435,7 @@ BEGIN IDS_2099 "Joystick padrão de 8 botões" IDS_2100 "CH Flightstick Pro" IDS_2101 "Microsoft SideWinder Pad" - IDS_2102 "Thrustmaster Flight Control System" + IDS_2102 "Sistema de Controle de Voo Thrustmaster" IDS_2103 "Nada" IDS_2104 "Não foi possível carregar os aceleradores do teclado." IDS_2105 "Não foi possível registrar a entrada bruta."