pax_global_header00006660000000000000000000000064147627617520014533gustar00rootroot0000000000000052 comment=8df6d723ae3afc28ebc0542eaab27709c50cb9a0 BambooTracker-0.6.5/000077500000000000000000000000001476276175200142565ustar00rootroot00000000000000BambooTracker-0.6.5/.appveyor.yml000066400000000000000000000127361476276175200167350ustar00rootroot00000000000000#---------------------------------# # general configuration # #---------------------------------# # version format version: 0.6.5.{build} # branches to build branches: # whitelist only: - master # - /v\d*\.\d*\.\d*/ # Skipping commits with particular message or from specific user skip_commits: message: /Created.*\.(png|jpg|jpeg|bmp|gif|md)/ # Regex for matching commit message files: - '*.md' - '*.txt' - 'LICENSE' - '.gitignore' #---------------------------------# # environment configuration # #---------------------------------# # set clone depth clone_depth: 3 # clone entire repository history if not defined environment: APPVEYOR_YML_DISABLE_PS_LINUX: true matrix: # Windows 7 or later - APPVEYOR_JOB_NAME: for Windows (7 or later) APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 COMPILER: C:\Qt\Tools\mingw810_32\bin QT: C:\Qt\5.15.2\mingw81_32\bin PLATFORM: windows DEST: BambooTracker-v%APPVEYOR_BUILD_VERSION%-dev-windows-7-32bit.zip MAKE: mingw32-make RELEASE_BUILD: false # Windows XP (Debug) # - APPVEYOR_JOB_NAME: for Windows XP # APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 # COMPILER: C:\Qt\Tools\mingw492_32\bin # QT: C:\Qt\5.5\mingw492_32\bin # PLATFORM: windows-xp # DEST: BambooTracker-v%APPVEYOR_BUILD_VERSION%-dev-windows-xp-32bit.zip # MAKE: mingw32-make # RELEASE_BUILD: false # Windows XP (Release) # - APPVEYOR_JOB_NAME: for Windows XP (Release) # APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 # COMPILER: C:\Qt\Tools\mingw492_32\bin # QT: C:\Qt\5.5\mingw492_32\bin # PLATFORM: windows-xp # DEST: BambooTracker-v%APPVEYOR_BUILD_VERSION%-windows-xp-32bit.zip # MAKE: mingw32-make # RELEASE_BUILD: true # macOS Ventura - APPVEYOR_JOB_NAME: for macOS APPVEYOR_BUILD_WORKER_IMAGE: macos-ventura PLATFORM: macos DEST: BambooTracker-v$APPVEYOR_BUILD_VERSION-dev-macos-64bit.zip MAKE: make RELEASE_BUILD: false for: - # Debug matrix: only: - RELEASE_BUILD: false skip_tags: true # - # # Release # matrix: # only: # - RELEASE_BUILD: true # skip_non_tags: true # scripts that run after cloning repository install: - git submodule init - git submodule update #---------------------------------# # build configuration # #---------------------------------# # build platform, i.e. x86, x64, Any CPU. This setting is optional. platform: x86 # scripts to run before build before_build: - ps: $env:Path = "$env:QT;$env:COMPILER;$env:Path" - sh: | # Update outdated cacerts curl --insecure --remote-name https://curl.se/ca/cacert.pem sudo mv cacert.pem /etc/ssl/cert.pem export HOMEBREW_NO_INSTALL_CLEANUP=1 brew update # Download failed: Homebrew-installed `curl` is not installed brew install curl brew install pkg-config jack p7zip export PATH="$HOME/Qt/6.6/macos/bin:$PATH" export PKG_CONFIG_PATH="/usr/local/opt/jack/lib/pkgconfig"${PKG_CONFIG_PATH:+':'}$PKG_CONFIG_PATH # to run your custom scripts instead of automatic MSBuild build_script: - ps: | $QMAKE_CONFIGS = if ($env:RELEASE_BUILD -ieq "true") { "CONFIG+=release CONFIG-=debug" } else { "CONFIG-=release CONFIG+=debug CONFIG+=console CONFIG+=nostrip" } echo $QMAKE_CONFIGS Invoke-Expression ("qmake.exe Project.pro PREFIX=$pwd\target " + $QMAKE_CONFIGS) Invoke-Expression ($env:MAKE + " qmake_all") Invoke-Expression ($env:MAKE + " -j2") - sh: | qmake Project.pro PREFIX="$APPVEYOR_BUILD_FOLDER"/target CONFIG-=release CONFIG+=debug CONFIG+=console CONFIG+=nostrip CONFIG+=install_flat CONFIG+=use_jack CONFIG+=no_warnings_are_errors $MAKE qmake_all $MAKE -j2 # scripts to run after build (working directory and environment changes are persisted from the previous steps) after_build: - ps: Invoke-Expression ($env:MAKE + " install") - sh: $MAKE install - cd target # Port of scripts/package_windows.sh to PowerShell - ps: | Copy-Item -Recurse ..\*.md . $HELP_OUT = (windeployqt.exe -h) $DEPLOY_OPTS = "-verbose=2" $PLUGIN_OPTS = @("--no-quick-import","--no-system-d3d-compiler","--no-webkit2","--no-opengl-sw","--no-virtualkeyboard","--no-angle") $PLUGIN_OPTS | ForEach{If ($HELP_OUT | Select-String $_) { $DEPLOY_OPTS = $DEPLOY_OPTS + " " + $_ }} $EXLIB_OPTS = @("svg") $EXLIB_OPTS | ForEach{If ($HELP_OUT | Select-String $_) { $DEPLOY_OPTS = $DEPLOY_OPTS + " --no-" + $_ }} Invoke-Expression ("windeployqt.exe BambooTracker.exe " + $DEPLOY_OPTS) Copy-Item -Recurse translations\*.qm lang # Save qico* library for working .ico support! Move-Item imageformats\*qico* .\ Remove-Item -Recurse -Force imageformats\*,translations Move-Item *qico* imageformats\ - sh: bash ../scripts/package_osx.sh - cd .. - ps: | $DEV_LAB = if ($env:RELEASE_BUILD -ieq "true") { "-" } else { "-dev-" } mv target BambooTracker-v"$env:APPVEYOR_BUILD_VERSION$DEV_LAB$env:PLATFORM" 7z a -tzip "$env:DEST" BambooTracker-v"$env:APPVEYOR_BUILD_VERSION$DEV_LAB$env:PLATFORM" - sh: | mv target BambooTracker-v"$APPVEYOR_BUILD_VERSION"-dev-"$PLATFORM" 7z a -tzip "$DEST" BambooTracker-v"$APPVEYOR_BUILD_VERSION"-dev-"$PLATFORM" #---------------------------------# # artifacts configuration # #---------------------------------# artifacts: # pushing a single file - path: $(DEST) BambooTracker-0.6.5/.gitattributes000066400000000000000000000047261476276175200171620ustar00rootroot00000000000000############################################################################### # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto ############################################################################### # Set default behavior for command prompt diff. # # This is need for earlier builds of msysgit that does not have it on by # default for csharp files. # Note: This is only used by command line ############################################################################### #*.cs diff=csharp ############################################################################### # Set the merge driver for project and solution files # # Merging from the command prompt will add diff markers to the files if there # are conflicts (Merging from VS is not affected by the settings below, in VS # the diff markers are never inserted). Diff markers may cause the following # file extensions to fail to load in VS. An alternative would be to treat # these files as binary and thus will always conflict and require user # intervention with every merge. To do so, just uncomment the entries below ############################################################################### #*.sln merge=binary #*.csproj merge=binary #*.vbproj merge=binary #*.vcxproj merge=binary #*.vcproj merge=binary #*.dbproj merge=binary #*.fsproj merge=binary #*.lsproj merge=binary #*.wixproj merge=binary #*.modelproj merge=binary #*.sqlproj merge=binary #*.wwaproj merge=binary ############################################################################### # behavior for image files # # image files are treated as binary by default. ############################################################################### #*.jpg binary #*.png binary #*.gif binary ############################################################################### # diff behavior for common document formats # # Convert binary document formats to text before diffing them. This feature # is only available from the command line. Turn it on by uncommenting the # entries below. ############################################################################### #*.doc diff=astextplain #*.DOC diff=astextplain #*.docx diff=astextplain #*.DOCX diff=astextplain #*.dot diff=astextplain #*.DOT diff=astextplain #*.pdf diff=astextplain #*.PDF diff=astextplain #*.rtf diff=astextplain #*.RTF diff=astextplain BambooTracker-0.6.5/.github/000077500000000000000000000000001476276175200156165ustar00rootroot00000000000000BambooTracker-0.6.5/.github/ISSUE_TEMPLATE/000077500000000000000000000000001476276175200200015ustar00rootroot00000000000000BambooTracker-0.6.5/.github/ISSUE_TEMPLATE/bug-report.md000066400000000000000000000024741476276175200224200ustar00rootroot00000000000000--- name: Bug Report about: Report a bug in/around BambooTracker title: "[Windows|macOS|Linux|BSD] - Short bug summary" labels: bug assignees: '' --- > #### Checklist > > > > - [ ] I am reporting exactly 1 bug with this issue. > - [ ] This bug hasn't already been reported. > - [ ] This bug hasn't already been fixed in the latest development build. --- ## Bug Description ## How to reproduce 1. … 2. … 3. … ## System Information - **Operating System**: - **BambooTracker Version**: - **Build Type**: BambooTracker-0.6.5/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000000341476276175200217660ustar00rootroot00000000000000blank_issues_enabled: false BambooTracker-0.6.5/.github/ISSUE_TEMPLATE/feature-request.md000066400000000000000000000013601476276175200234440ustar00rootroot00000000000000--- name: Feature Request about: Suggest a feature / QOL improvement title: "[Feature] - Short feature summary" labels: enhancement assignees: '' --- > #### Checklist > > > > - [ ] I'm requesting exactly 1 feature with this issue. > - [ ] This feature hasn't already been requested. > - [ ] This feature hasn't already been implemented in the latest development build. --- ## Requested Feature BambooTracker-0.6.5/.github/workflows/000077500000000000000000000000001476276175200176535ustar00rootroot00000000000000BambooTracker-0.6.5/.github/workflows/macos.yml000066400000000000000000000102521476276175200215000ustar00rootroot00000000000000name: macOS on: push: branches: master tags: 'v*.*.*' paths-ignore: - '**.md' - '**.txt' - 'LICENSE' - '.gitignore' pull_request: branches: master paths-ignore: - '**.md' - '**.txt' - 'LICENSE' - '.gitignore' defaults: run: shell: bash env: BUILD_TARGET: macos-64bit SCRIPT_NAME: osx MAKE: make BT_INSTALLBASE: ${{ github.workspace }}/target/ QT_VERSION: 6.6.1 QT_HOSTOS: mac_x64 QT_TOOLCHAIN: clang_64 QT_MODULES: qtbase qttools qttranslations qt5compat qtdeclarative QT_INSTALLBASE: ${{ github.workspace }}/Qt/ QMAKE_EXTRA_ARGUMENTS: CONFIG+=install_flat CONFIG+=use_jack CONFIG+=no_warnings_are_errors jobs: build: runs-on: macos-13 steps: - name: Identify build type. id: identify-build run: | case ${GITHUB_REF} in refs/tags/* ) TAG=${GITHUB_REF#refs/tags/} echo "Release ${TAG}" echo "build-tag=${TAG}" >> $GITHUB_OUTPUT echo "release=true" >> $GITHUB_OUTPUT ;; refs/pull/*) PR=$(echo ${GITHUB_REF} | cut -d/ -f3) echo "Test PR #${PR}" echo "build-tag=pr-${PR}" >> $GITHUB_OUTPUT echo "release=false" >> $GITHUB_OUTPUT ;; refs/heads/* ) BRANCH=${GITHUB_REF#refs/heads/} echo "Test ${BRANCH}" echo "build-tag=${BRANCH}" >> $GITHUB_OUTPUT echo "release=false" >> $GITHUB_OUTPUT ;; * ) echo "Test (unknown)" echo "build-tag=unknown" >> $GITHUB_OUTPUT echo "release=false" >> $GITHUB_OUTPUT ;; esac - name: Checking out repository. uses: actions/checkout@v4 with: submodules: recursive ## macOS-specific steps - name: Pin Xcode version run: sudo xcode-select -s "/Applications/Xcode_15.2.app" # Cache Qt installations, very costly & flakey to fetch - name: Fetching Qt cache. id: qt-cache uses: actions/cache@v4 with: path: ${{ env.QT_INSTALLBASE }} key: ${{ runner.os }} Qt${{ env.QT_VERSION }} ${{ env.QT_TOOLCHAIN }} ${{ env.QT_MODULES }} - name: Installing Qt. run: | ./scripts/fetch_qt.sh ${QT_INSTALLBASE} ${QT_VERSION} ${QT_HOSTOS} ${QT_TOOLCHAIN} ${QT_MODULES} - name: Adding Qt to PATH. run: | find ${QT_INSTALLBASE} -type d -name bin >> ${GITHUB_PATH} - name: Installing dependencies. run: | export HOMEBREW_NO_INSTALL_CLEANUP=1 brew update brew install pkg-config jack coreutils ## End macOS-specific steps - name: Configuring. run: | lupdate Project.pro qmake Project.pro PREFIX=${BT_INSTALLBASE} \ CONFIG+=release CONFIG-=debug ${QMAKE_EXTRA_ARGUMENTS} ${MAKE} qmake_all - name: Building. run: | ${MAKE} -j2 - name: Installing. run: | ${MAKE} -j2 install - name: Test packaging. if: env.DONT_PACKAGE != 'true' run: | pushd ${BT_INSTALLBASE} bash ${GITHUB_WORKSPACE}/scripts/package_${SCRIPT_NAME:-${BUILD_TARGET%%-*}}.sh popd - name: Finalize packaging. id: packaging if: steps.identify-build.outputs.release == 'true' && env.DONT_PACKAGE != 'true' run: | export package_name="BambooTracker-${{ steps.identify-build.outputs.build-tag }}-${BUILD_TARGET}" echo "package-name=${package_name}" >> $GITHUB_OUTPUT mv -v ${BT_INSTALLBASE} ${package_name} tar -cvf- \ ${package_name} | gzip -9c > ${package_name}.tar.gz - name: Upload release package. if: steps.identify-build.outputs.release == 'true' && env.DONT_PACKAGE != 'true' uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: ${{ steps.packaging.outputs.package-name }}.tar.gz asset_name: ${{ steps.packaging.outputs.package-name }}.tar.gz tag: ${{ github.ref }} BambooTracker-0.6.5/.github/workflows/nixpkgs.yml000066400000000000000000000073351476276175200220710ustar00rootroot00000000000000name: Linux (Nixpkgs) on: push: branches: master tags: 'v*.*.*' paths-ignore: - '**.md' - '**.txt' - 'LICENSE' - '.gitignore' pull_request: branches: master paths-ignore: - '**.md' - '**.txt' - 'LICENSE' - '.gitignore' defaults: run: shell: bash env: BUILD_TARGET: linux-64bit NIXPKGS_CHANNEL: nixos-23.05 BT_INSTALLBASE: ${{ github.workspace }}/target/ jobs: build: runs-on: ubuntu-latest steps: - name: Identify build type. id: identify-build run: | case ${GITHUB_REF} in refs/tags/* ) TAG=${GITHUB_REF#refs/tags/} echo "Release ${TAG}" echo "build-tag=${TAG}" >> $GITHUB_OUTPUT echo "release=true" >> $GITHUB_OUTPUT echo "buildVersion=${TAG#v}" >> $GITHUB_OUTPUT # nixpkgs ;; refs/pull/*) PR=$(echo ${GITHUB_REF} | cut -d/ -f3) echo "Test PR #${PR}" echo "build-tag=pr-${PR}" >> $GITHUB_OUTPUT echo "release=false" >> $GITHUB_OUTPUT echo "buildVersion=pr-${PR}-${GITHUB_SHA}" >> $GITHUB_OUTPUT # nixpkgs ;; refs/heads/* ) BRANCH=${GITHUB_REF#refs/heads/} echo "Test ${BRANCH}" echo "build-tag=${BRANCH}" >> $GITHUB_OUTPUT echo "release=false" >> $GITHUB_OUTPUT echo "buildVersion=${BRANCH}-${GITHUB_SHA}" >> $GITHUB_OUTPUT # nixpkgs ;; * ) echo "Test (unknown)" echo "build-tag=unknown" >> $GITHUB_OUTPUT echo "release=false" >> $GITHUB_OUTPUT echo "buildVersion=unknown-${GITHUB_SHA}" >> $GITHUB_OUTPUT # nixpkgs ;; esac - name: Checking out repository. uses: actions/checkout@v4 with: submodules: recursive ## Nixpkgs-specific steps - name: Installing Nix. uses: cachix/install-nix-action@v18 with: nix_path: "nixpkgs=channel:${{ env.NIXPKGS_CHANNEL }}" - name: Show Nixpkgs version. run: nix-instantiate --eval -E '(import {}).lib.version' - name: Building. run: | nix-build scripts/build_nixpkgs_local.nix --argstr buildVersion "${{ steps.identify-build.outputs.buildVersion }}" -A build --no-out-link > ../outlink - name: Test packaging. if: env.DONT_PACKAGE != 'true' run: | nix-build scripts/build_nixpkgs_local.nix --argstr buildVersion "${{ steps.identify-build.outputs.buildVersion }}" -A bundle install -Dm755 $(realpath result) ${BT_INSTALLBASE}/bin/BambooTracker cp -r --no-preserve=all "$(cat ../outlink)/share" ${BT_INSTALLBASE}/share rm -rf ${BT_INSTALLBASE}/share/BambooTracker/lang # bundled into binary, presumably ## End Nixpkgs-specific steps - name: Finalize packaging. id: packaging if: steps.identify-build.outputs.release == 'true' && env.DONT_PACKAGE != 'true' run: | export package_name="BambooTracker-${{ steps.identify-build.outputs.build-tag }}-${BUILD_TARGET}" echo "package-name=${package_name}" >> $GITHUB_OUTPUT mv -v ${BT_INSTALLBASE} ${package_name} 7z a -tzip ${package_name}{.zip,} - name: Upload release package. if: steps.identify-build.outputs.release == 'true' && env.DONT_PACKAGE != 'true' uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: ${{ steps.packaging.outputs.package-name }}.zip asset_name: ${{ steps.packaging.outputs.package-name }}.zip tag: ${{ github.ref }} BambooTracker-0.6.5/.github/workflows/src-tarball.yml000066400000000000000000000027321476276175200226100ustar00rootroot00000000000000name: Upload Source Tarball on: push: tags: 'v*.*.*' defaults: run: shell: bash jobs: build: runs-on: ubuntu-latest steps: - name: Identify build type. id: identify-build run: | TAG=${GITHUB_REF#refs/tags/} echo "Release ${TAG}" echo "build-tag=${TAG}" >> $GITHUB_OUTPUT - name: Checking out repository. uses: actions/checkout@v4 with: submodules: recursive - name: Package sources. id: packaging run: | export package_name="BambooTracker-src-${{ steps.identify-build.outputs.build-tag }}" echo "package-name=${package_name}" >> $GITHUB_OUTPUT export checkout_name="$(basename ${PWD})" pushd .. mv ${checkout_name} ${package_name} tar -cvf- --sort=name \ --exclude=${package_name}.tar.gz \ --exclude=.git \ --exclude=.gitattributes \ --exclude=.gitmodules \ --exclude=.gitignore \ ${package_name} | gzip -9c > ${package_name}/${package_name}.tar.gz mv ${package_name} ${checkout_name} popd - name: Upload source tarball. uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: ${{ steps.packaging.outputs.package-name }}.tar.gz asset_name: ${{ steps.packaging.outputs.package-name }}.tar.gz tag: ${{ github.ref }} BambooTracker-0.6.5/.github/workflows/ubuntu.yml000066400000000000000000000065171476276175200217310ustar00rootroot00000000000000name: Linux (Ubuntu) on: push: branches: master tags: 'v*.*.*' paths-ignore: - '**.md' - '**.txt' - 'LICENSE' - '.gitignore' pull_request: branches: master paths-ignore: - '**.md' - '**.txt' - 'LICENSE' - '.gitignore' defaults: run: shell: bash env: BUILD_TARGET: linux DONT_PACKAGE: true MAKE: make BT_INSTALLBASE: ${{ github.workspace }}/target/ QMAKE_EXTRA_ARGUMENTS: CONFIG+=use_pulse CONFIG+=use_jack jobs: build: runs-on: ubuntu-22.04 steps: - name: Identify build type. id: identify-build run: | case ${GITHUB_REF} in refs/tags/* ) TAG=${GITHUB_REF#refs/tags/} echo "Release ${TAG}" echo "build-tag=${TAG}" >> $GITHUB_OUTPUT echo "release=true" >> $GITHUB_OUTPUT ;; refs/pull/*) PR=$(echo ${GITHUB_REF} | cut -d/ -f3) echo "Test PR #${PR}" echo "build-tag=pr-${PR}" >> $GITHUB_OUTPUT echo "release=false" >> $GITHUB_OUTPUT ;; refs/heads/* ) BRANCH=${GITHUB_REF#refs/heads/} echo "Test ${BRANCH}" echo "build-tag=${BRANCH}" >> $GITHUB_OUTPUT echo "release=false" >> $GITHUB_OUTPUT ;; * ) echo "Test (unknown)" echo "build-tag=unknown" >> $GITHUB_OUTPUT echo "release=false" >> $GITHUB_OUTPUT ;; esac - name: Checking out repository. uses: actions/checkout@v4 with: submodules: recursive ## Ubuntu-specific steps - name: Installing dependencies. run: | sudo apt update sudo apt install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools \ libasound2-dev libpulse-dev libjack-jackd2-dev ## End Ubuntu-specific steps - name: Configuring. run: | lupdate Project.pro qmake Project.pro PREFIX=${BT_INSTALLBASE} \ CONFIG+=release CONFIG-=debug ${QMAKE_EXTRA_ARGUMENTS} ${MAKE} qmake_all - name: Building. run: | ${MAKE} -j2 - name: Installing. run: | ${MAKE} -j2 install - name: Test packaging. if: env.DONT_PACKAGE != 'true' run: | pushd ${BT_INSTALLBASE} bash ${GITHUB_WORKSPACE}/scripts/package_${SCRIPT_NAME:-${BUILD_TARGET%%-*}}.sh popd - name: Finalize packaging. id: packaging if: steps.identify-build.outputs.release == 'true' && env.DONT_PACKAGE != 'true' run: | export package_name="BambooTracker-${{ steps.identify-build.outputs.build-tag }}-${BUILD_TARGET}" echo "package-name=${package_name}" >> $GITHUB_OUTPUT mv -v ${BT_INSTALLBASE} ${package_name} 7z a -tzip ${package_name}{.zip,} - name: Upload release package. if: steps.identify-build.outputs.release == 'true' && env.DONT_PACKAGE != 'true' uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: ${{ steps.packaging.outputs.package-name }}.zip asset_name: ${{ steps.packaging.outputs.package-name }}.zip tag: ${{ github.ref }} BambooTracker-0.6.5/.github/workflows/windows-10-qt6.yml000066400000000000000000000126251476276175200230240ustar00rootroot00000000000000name: Windows 10 (64-bit, Qt6) on: push: branches: master tags: 'v*.*.*' paths-ignore: - '**.md' - '**.txt' - 'LICENSE' - '.gitignore' pull_request: branches: master paths-ignore: - '**.md' - '**.txt' - 'LICENSE' - '.gitignore' defaults: run: shell: bash env: BUILD_TARGET: windows-10-64bit MAKE: mingw32-make BT_INSTALLBASE: ${{ github.workspace }}/target/ MINGW_VERSION: 11.2.0 MINGW_BITNESS: 64 MINGW_CHOCOBASE: /c/ProgramData/chocolatey/lib/mingw/tools/install/mingw64 MINGW_INSTALLBASE: ${{ github.workspace }}/mingw64/ QT_VERSION: 6.4.0 QT_HOSTOS: windows_x86 QT_TOOLCHAIN: win64_mingw QT_MODULES: qtbase qttools qttranslations qt5compat qtdeclarative QT_INSTALLBASE: ${{ github.workspace }}/Qt/ jobs: build: runs-on: windows-latest steps: - name: Identify build type. id: identify-build run: | case ${GITHUB_REF} in refs/tags/* ) TAG=${GITHUB_REF#refs/tags/} echo "Release ${TAG}" echo "build-tag=${TAG}" >> $GITHUB_OUTPUT echo "release=true" >> $GITHUB_OUTPUT ;; refs/pull/*) PR=$(echo ${GITHUB_REF} | cut -d/ -f3) echo "Test PR #${PR}" echo "build-tag=pr-${PR}" >> $GITHUB_OUTPUT echo "release=false" >> $GITHUB_OUTPUT ;; refs/heads/* ) BRANCH=${GITHUB_REF#refs/heads/} echo "Test ${BRANCH}" echo "build-tag=${BRANCH}" >> $GITHUB_OUTPUT echo "release=false" >> $GITHUB_OUTPUT ;; * ) echo "Test (unknown)" echo "build-tag=unknown" >> $GITHUB_OUTPUT echo "release=false" >> $GITHUB_OUTPUT ;; esac - name: Checking out repository. uses: actions/checkout@v4 with: submodules: recursive ## Windows-specific steps # Unable to cache system-installed MinGW. Uploaded cache is supposedly an empty tarball # when trying to fetch it. # > Received 30 of 30 (100.0%), 0.0 MBs/sec # > Cache Size: ~0 MB (30 B) # We instead copy & reown the MinGW installation to the GitHub workspace, # ugly but it works. :/ - name: Fetching MinGW cache. id: mingw-cache uses: actions/cache@v4 with: path: ${{ env.MINGW_INSTALLBASE }} key: ${{ env.MINGW_VERSION }} ${{ env.MINGW_BITNESS }} - name: Installing MinGW. run: | ./scripts/fetch_mingw.sh ${MINGW_CHOCOBASE} ${MINGW_VERSION} ${MINGW_BITNESS} ${MINGW_INSTALLBASE} # "Prepending to PATH" doesn't work properly and inserts the path *close* # to the start of PATH, but *after* the system's MinGW path. # Our MinGW PATH is not searched early enough to work for this build # so we either manually reexport PATH for ever step or fiddle with the PATH even more. :/ # - name: Adding MinGW to PATH. # run: | # echo ${MINGW_CHOCOBASE}/bin >> ${GITHUB_PATH} # Cache Qt installations, very costly & flakey to fetch - name: Fetching Qt cache. id: qt-cache uses: actions/cache@v4 with: path: ${{ env.QT_INSTALLBASE }} key: ${{ runner.os }} Qt${{ env.QT_VERSION }} ${{ env.QT_TOOLCHAIN }} ${{ env.QT_MODULES }} - name: Installing Qt. run: | ./scripts/fetch_qt.sh ${QT_INSTALLBASE} ${QT_VERSION} ${QT_HOSTOS} ${QT_TOOLCHAIN} ${QT_MODULES} - name: Adding Qt to PATH. run: | find ${QT_INSTALLBASE} -type d -name bin >> ${GITHUB_PATH} ## End Windows-specific steps - name: Configuring. run: | export PATH="${MINGW_INSTALLBASE}/bin:${PATH}" lupdate Project.pro mkdir build pushd build cmake .. -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX=${BT_INSTALLBASE} -DCMAKE_BUILD_TYPE=Release -DRTAUDIO_API_DS=ON popd - name: Building. run: | export PATH="${MINGW_INSTALLBASE}/bin:${PATH}" pushd build ${MAKE} -j2 popd - name: Installing. run: | export PATH="${MINGW_INSTALLBASE}/bin:${PATH}" pushd build ${MAKE} -j2 install popd - name: Test packaging. if: env.DONT_PACKAGE != 'true' run: | export PATH="${MINGW_INSTALLBASE}/bin:${PATH}" pushd ${BT_INSTALLBASE} bash ${GITHUB_WORKSPACE}/scripts/package_${SCRIPT_NAME:-${BUILD_TARGET%%-*}}.sh popd - name: Finalize packaging. id: packaging if: steps.identify-build.outputs.release == 'true' && env.DONT_PACKAGE != 'true' run: | export package_name="BambooTracker-${{ steps.identify-build.outputs.build-tag }}-${BUILD_TARGET}" echo "package-name=${package_name}" >> $GITHUB_OUTPUT mv -v ${BT_INSTALLBASE} ${package_name} 7z a -tzip ${package_name}{.zip,} - name: Upload release package. if: steps.identify-build.outputs.release == 'true' && env.DONT_PACKAGE != 'true' uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: ${{ steps.packaging.outputs.package-name }}.zip asset_name: ${{ steps.packaging.outputs.package-name }}.zip tag: ${{ github.ref }} BambooTracker-0.6.5/.github/workflows/windows.yml000066400000000000000000000124251476276175200220740ustar00rootroot00000000000000name: Windows 7 and up (32-bit, Qt5) on: push: branches: master tags: 'v*.*.*' paths-ignore: - '**.md' - '**.txt' - 'LICENSE' - '.gitignore' pull_request: branches: master paths-ignore: - '**.md' - '**.txt' - 'LICENSE' - '.gitignore' defaults: run: shell: bash env: BUILD_TARGET: windows-7-32bit MAKE: mingw32-make BT_INSTALLBASE: ${{ github.workspace }}/target/ MINGW_VERSION: 8.1.0 MINGW_BITNESS: 32 MINGW_CHOCOBASE: /c/ProgramData/chocolatey/lib/mingw/tools/install/mingw32 MINGW_INSTALLBASE: ${{ github.workspace }}/mingw32/ QT_VERSION: 5.15.2 QT_HOSTOS: windows_x86 QT_TOOLCHAIN: win32_mingw81 QT_MODULES: qtbase qttools qttranslations QT_INSTALLBASE: ${{ github.workspace }}/Qt/ jobs: build: runs-on: windows-latest steps: - name: Identify build type. id: identify-build run: | case ${GITHUB_REF} in refs/tags/* ) TAG=${GITHUB_REF#refs/tags/} echo "Release ${TAG}" echo "build-tag=${TAG}" >> $GITHUB_OUTPUT echo "release=true" >> $GITHUB_OUTPUT ;; refs/pull/*) PR=$(echo ${GITHUB_REF} | cut -d/ -f3) echo "Test PR #${PR}" echo "build-tag=pr-${PR}" >> $GITHUB_OUTPUT echo "release=false" >> $GITHUB_OUTPUT ;; refs/heads/* ) BRANCH=${GITHUB_REF#refs/heads/} echo "Test ${BRANCH}" echo "build-tag=${BRANCH}" >> $GITHUB_OUTPUT echo "release=false" >> $GITHUB_OUTPUT ;; * ) echo "Test (unknown)" echo "build-tag=unknown" >> $GITHUB_OUTPUT echo "release=false" >> $GITHUB_OUTPUT ;; esac - name: Checking out repository. uses: actions/checkout@v4 with: submodules: recursive ## Windows-specific steps # Unable to cache system-installed MinGW. Uploaded cache is supposedly an empty tarball # when trying to fetch it. # > Received 30 of 30 (100.0%), 0.0 MBs/sec # > Cache Size: ~0 MB (30 B) # We instead copy & reown the MinGW installation to the GitHub workspace, # ugly but it works. :/ - name: Fetching MinGW cache. id: mingw-cache uses: actions/cache@v4 with: path: ${{ env.MINGW_INSTALLBASE }} key: ${{ env.MINGW_VERSION }} ${{ env.MINGW_BITNESS }} - name: Installing MinGW. run: | ./scripts/fetch_mingw.sh ${MINGW_CHOCOBASE} ${MINGW_VERSION} ${MINGW_BITNESS} ${MINGW_INSTALLBASE} # "Prepending to PATH" doesn't work properly and inserts the path *close* # to the start of PATH, but *after* the system's MinGW path. # Our MinGW PATH is not searched early enough to work for this build # so we either manually reexport PATH for ever step or fiddle with the PATH even more. :/ # - name: Adding MinGW to PATH. # run: | # echo ${MINGW_CHOCOBASE}/bin >> ${GITHUB_PATH} # Cache Qt installations, very costly & flakey to fetch - name: Fetching Qt cache. id: qt-cache uses: actions/cache@v4 with: path: ${{ env.QT_INSTALLBASE }} key: ${{ runner.os }} Qt${{ env.QT_VERSION }} ${{ env.QT_TOOLCHAIN }} ${{ env.QT_MODULES }} - name: Installing Qt. run: | ./scripts/fetch_qt.sh ${QT_INSTALLBASE} ${QT_VERSION} ${QT_HOSTOS} ${QT_TOOLCHAIN} ${QT_MODULES} - name: Adding Qt to PATH. run: | find ${QT_INSTALLBASE} -type d -name bin >> ${GITHUB_PATH} ## End Windows-specific steps - name: Configuring. run: | export PATH="${MINGW_INSTALLBASE}/bin:${PATH}" lupdate Project.pro qmake Project.pro PREFIX=${BT_INSTALLBASE} \ CONFIG+=release CONFIG-=debug ${QMAKE_EXTRA_ARGUMENTS} ${MAKE} qmake_all - name: Building. run: | export PATH="${MINGW_INSTALLBASE}/bin:${PATH}" ${MAKE} -j2 - name: Installing. run: | export PATH="${MINGW_INSTALLBASE}/bin:${PATH}" ${MAKE} -j2 install - name: Test packaging. if: env.DONT_PACKAGE != 'true' run: | export PATH="${MINGW_INSTALLBASE}/bin:${PATH}" pushd ${BT_INSTALLBASE} bash ${GITHUB_WORKSPACE}/scripts/package_${SCRIPT_NAME:-${BUILD_TARGET%%-*}}.sh popd - name: Finalize packaging. id: packaging if: steps.identify-build.outputs.release == 'true' && env.DONT_PACKAGE != 'true' run: | export package_name="BambooTracker-${{ steps.identify-build.outputs.build-tag }}-${BUILD_TARGET}" echo "package-name=${package_name}" >> $GITHUB_OUTPUT mv -v ${BT_INSTALLBASE} ${package_name} 7z a -tzip ${package_name}{.zip,} - name: Upload release package. if: steps.identify-build.outputs.release == 'true' && env.DONT_PACKAGE != 'true' uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: ${{ steps.packaging.outputs.package-name }}.zip asset_name: ${{ steps.packaging.outputs.package-name }}.zip tag: ${{ github.ref }} BambooTracker-0.6.5/.gitignore000066400000000000000000000056631476276175200162600ustar00rootroot00000000000000# Created by https://www.gitignore.io/api/qt,c++,linux,macos,emacs,windows,visualstudiocode # Edit at https://www.gitignore.io/?templates=qt,c++,linux,macos,emacs,windows,visualstudiocode ### C++ ### # Prerequisites *.d # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran module files *.mod *.smod # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app ### Emacs ### # -*- mode: gitignore; -*- *~ \#*\# /.emacs.desktop /.emacs.desktop.lock *.elc auto-save-list tramp .\#* # Org-mode .org-id-locations *_archive # flymake-mode *_flymake.* # eshell files /eshell/history /eshell/lastdir # elpa packages /elpa/ # reftex files *.rel # AUCTeX auto folder /auto/ # cask packages .cask/ dist/ # Flycheck flycheck_*.el # server auth directory /server/ # projectiles files .projectile # directory configuration .dir-locals.el # network security /network-security.data ### Linux ### # temporary files which can be created if a process still has a handle open of a deleted file .fuse_hidden* # KDE directory preferences .directory # Linux trash folder which might appear on any partition or disk .Trash-* # .nfs files are created when an open file is removed but is still being accessed .nfs* ### macOS ### # General .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### Qt ### # C++ objects and libs # Qt-es object_script.*.Release object_script.*.Debug *_plugin_import.cpp .qmake.cache .qmake.stash *.pro.user *.pro.user.* *.qbs.user *.qbs.user.* *.moc moc_*.cpp moc_*.h qrc_*.cpp ui_*.h *.qmlc *.jsc Makefile* *build-* .qm config.log *.prl # Qt unit tests target_wrapper.* # QtCreator *.autosave # QtCreator Qml *.qmlproject.user *.qmlproject.user.* # QtCreator CMake CMakeLists.txt.user* # QtCreator 4.8< compilation database compile_commands.json # QtCreator local machine specific files for imported projects *creator.user* ### VisualStudioCode ### .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ### VisualStudioCode Patch ### # Ignore all local history of files .history ### Windows ### # Windows thumbnail cache files Thumbs.db Thumbs.db:encryptable ehthumbs.db ehthumbs_vista.db # Dump file *.stackdump # Folder config file [Dd]esktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msix *.msm *.msp # Windows shortcuts *.lnk # End of https://www.gitignore.io/api/qt,c++,linux,macos,emacs,windows,visualstudiocode ########## !/BambooTracker/res/icon BambooTracker/BambooTracker qmake/config.tests/*/test BambooTracker-0.6.5/.gitmodules000066400000000000000000000005311476276175200164320ustar00rootroot00000000000000[submodule "submodules/RtAudio/src"] path = submodules/RtAudio/src url = https://github.com/thestk/rtaudio [submodule "submodules/RtMidi/src"] path = submodules/RtMidi/src url = https://github.com/thestk/rtmidi [submodule "submodules/emu2149/src"] path = submodules/emu2149/src url = https://github.com/rerrahkr/emu2149 branch = opna-ssg BambooTracker-0.6.5/BambooTracker/000077500000000000000000000000001476276175200167715ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/BambooTracker.pro000066400000000000000000000446401476276175200222360ustar00rootroot00000000000000#------------------------------------------------- # # Project created by QtCreator 2018-06-09T16:20:11 # #------------------------------------------------- QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets # Porting helper Qt5 -> Qt6 greaterThan(QT_MAJOR_VERSION, 5): QT += core5compat TARGET = BambooTracker TEMPLATE = app # The following define makes your compiler emit warnings if you use # any feature of Qt which has been marked as deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if you use deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 include("../qmake/variables.pri") INSTALLS += target win32|install_flat { target.path = $$PREFIX } else { target.path = $$PREFIX/bin } CONFIG += c++17 clang|if(gcc:!intel_icc) { QMAKE_CFLAGS += -std=gnu11 } # Temporary downgrades of problematic compiler flags here # CPP_WARNING_FLAGS += QMAKE_CFLAGS_WARN_ON += $$CPP_WARNING_FLAGS QMAKE_CXXFLAGS_WARN_ON += $$CPP_WARNING_FLAGS SOURCES += \ chip/blip_buf/blip_buf.c \ chip/mame/fmopn.c \ chip/mame/mame_2608.cpp \ chip/mame/ymdeltat.c \ chip/nuked/nuked_2608.cpp \ chip/register_write_logger.cpp \ chip/ymfm/ymfm_2608.cpp \ chip/ymfm/ymfm_adpcm.cpp \ chip/ymfm/ymfm_opn.cpp \ chip/ymfm/ymfm_ssg.cpp \ command/instrument/swap_instruments_command.cpp \ command/pattern/change_values_in_pattern_command.cpp \ command/pattern/paste_insert_copied_data_to_pattern_command.cpp \ command/pattern/pattern_command_utils.cpp \ command/pattern/set_key_cut_to_step_command.cpp \ command/pattern/transpose_note_in_pattern_command.cpp \ gui/bookmark_manager_form.cpp \ gui/command/instrument/instrument_command_qt_utils.cpp \ gui/command/instrument/swap_instruments_qt_command.cpp \ gui/command/order/order_list_common_qt_command.cpp \ gui/command/pattern/pattern_editor_common_qt_command.cpp \ gui/drop_detect_list_widget.cpp \ gui/effect_description.cpp \ gui/effect_list_dialog.cpp \ gui/file_io_error_message_box.cpp \ gui/font_info_widget.cpp \ gui/go_to_dialog.cpp \ gui/gui_utils.cpp \ gui/hide_tracks_dialog.cpp \ gui/instrument_editor/adpcm_address_spin_box.cpp \ gui/instrument_editor/adpcm_drumkit_editor.cpp \ gui/instrument_editor/adpcm_instrument_editor.cpp \ gui/instrument_editor/adpcm_sample_editor.cpp \ gui/instrument_editor/arpeggio_macro_editor.cpp \ gui/instrument_editor/fm_instrument_editor.cpp \ gui/instrument_editor/grid_settings_dialog.cpp \ gui/instrument_editor/instrument_editor.cpp \ gui/instrument_editor/instrument_editor_manager.cpp \ gui/instrument_editor/instrument_editor_utils.cpp \ gui/instrument_editor/pan_macro_editor.cpp \ gui/instrument_editor/sample_length_dialog.cpp \ gui/instrument_editor/ssg_instrument_editor.cpp \ gui/instrument_editor/tone_noise_macro_editor.cpp \ gui/key_signature_manager_form.cpp \ gui/keyboard_shortcut_list_dialog.cpp \ gui/note_name_manager.cpp \ gui/swap_tracks_dialog.cpp \ gui/transpose_song_dialog.cpp \ gui/wheel_spin_box.cpp \ instrument/sample_adpcm.cpp \ instrument/sequence_property.cpp \ io/btb_io.cpp \ io/bti_io.cpp \ io/btm_io.cpp \ io/dat_io.cpp \ io/dmp_io.cpp \ io/export_io.cpp \ io/ff_io.cpp \ io/ins_io.cpp \ io/io_utils.cpp \ io/opni_io.cpp \ io/p86_io.cpp \ io/pmb_io.cpp \ io/ppc_io.cpp \ io/pps_io.cpp \ io/pvi_io.cpp \ io/pzi_io.cpp \ io/raw_adpcm_io.cpp \ io/tfi_io.cpp \ io/vgi_io.cpp \ io/wav_container.cpp \ io/wopn_io.cpp \ io/y12_io.cpp \ jamming.cpp \ main.cpp \ gui/mainwindow.cpp \ chip/chip.cpp \ chip/opna.cpp \ chip/resampler.cpp \ chip/nuked/ym3438.c \ bamboo_tracker.cpp \ module/effect.cpp \ note.cpp \ playback.cpp \ song_length_calculator.cpp \ audio/audio_stream.cpp \ instrument/instruments_manager.cpp \ command/command_manager.cpp \ command/instrument/add_instrument_command.cpp \ command/instrument/remove_instrument_command.cpp \ gui/command/instrument/add_instrument_qt_command.cpp \ gui/command/instrument/remove_instrument_qt_command.cpp \ gui/instrument_editor/fm_operator_table.cpp \ gui/labeled_vertical_slider.cpp \ gui/labeled_horizontal_slider.cpp \ gui/slider_style.cpp \ gui/command/instrument/change_instrument_name_qt_command.cpp \ command/instrument/change_instrument_name_command.cpp \ opna_controller.cpp \ instrument/instrument.cpp \ instrument/envelope_fm.cpp \ gui/event_guard.cpp \ audio/audio_stream_rtaudio.cpp \ tick_counter.cpp \ module/module.cpp \ module/song.cpp \ module/pattern.cpp \ module/track.cpp \ module/step.cpp \ gui/order_list_editor/order_list_panel.cpp \ gui/order_list_editor/order_list_editor.cpp \ gui/pattern_editor/pattern_editor_panel.cpp \ gui/pattern_editor/pattern_editor.cpp \ command/pattern/set_key_off_to_step_command.cpp \ command/pattern/set_key_on_to_step_command.cpp \ command/pattern/set_instrument_to_step_command.cpp \ command/pattern/erase_instrument_in_step_command.cpp \ command/pattern/set_volume_to_step_command.cpp \ command/pattern/erase_volume_in_step_command.cpp \ command/pattern/set_effect_id_to_step_command.cpp \ command/pattern/erase_effect_in_step_command.cpp \ command/pattern/set_effect_value_to_step_command.cpp \ command/pattern/erase_effect_value_in_step_command.cpp \ command/pattern/insert_step_command.cpp \ command/pattern/delete_previous_step_command.cpp \ command/pattern/erase_step_command.cpp \ gui/command/instrument/deep_clone_instrument_qt_command.cpp \ command/instrument/deep_clone_instrument_command.cpp \ command/instrument/clone_instrument_command.cpp \ gui/command/instrument/clone_instrument_qt_command.cpp \ command/order/set_pattern_to_order_command.cpp \ command/order/insert_order_below_command.cpp \ command/order/delete_order_command.cpp \ command/pattern/paste_copied_data_to_pattern_command.cpp \ command/pattern/erase_cells_in_pattern_command.cpp \ command/order/paste_copied_data_to_order_command.cpp \ instrument/lfo_fm.cpp \ gui/instrument_editor/visualized_instrument_macro_editor.cpp \ instrument/effect_iterator.cpp \ command/pattern/paste_mix_copied_data_to_pattern_command.cpp \ gui/module_properties_dialog.cpp \ gui/groove_settings_dialog.cpp \ gui/configuration_dialog.cpp \ command/pattern/expand_pattern_command.cpp \ command/pattern/shrink_pattern_command.cpp \ instrument/abstract_instrument_property.cpp \ command/order/duplicate_order_command.cpp \ command/order/move_order_command.cpp \ command/order/clone_patterns_command.cpp \ command/order/clone_order_command.cpp \ command/pattern/set_echo_buffer_access_command.cpp \ gui/comment_edit_dialog.cpp \ io/binary_container.cpp \ command/pattern/interpolate_pattern_command.cpp \ command/pattern/reverse_pattern_command.cpp \ command/pattern/replace_instrument_in_pattern_command.cpp \ gui/vgm_export_settings_dialog.cpp \ gui/wave_export_settings_dialog.cpp \ configuration.cpp \ gui/configuration_handler.cpp \ gui/color_palette.cpp \ command/pattern/paste_overwrite_copied_data_to_pattern_command.cpp \ format/wopn_file.c \ instrument/bank.cpp \ gui/instrument_selection_dialog.cpp \ gui/s98_export_settings_dialog.cpp \ precise_timer.cpp \ io/module_io.cpp \ io/instrument_io.cpp \ io/bank_io.cpp \ gui/fm_envelope_set_edit_dialog.cpp \ gui/file_history.cpp \ midi/midi.cpp \ gui/q_application_wrapper.cpp \ gui/wave_visual.cpp HEADERS += \ bamboo_tracker_defs.hpp \ chip/2608_interface.hpp \ chip/blip_buf/blip_buf.h \ chip/c86ctl/cbus_boardtype.h \ chip/chip_defs.h \ chip/codec/ymb_codec.hpp \ chip/mame/fmopn.h \ chip/mame/fmopn_2608rom.h \ chip/mame/mame_2608.hpp \ chip/mame/mamedefs.h \ chip/mame/ymdeltat.h \ chip/nuked/nuked_2608.hpp \ chip/real_chip_interface.hpp \ chip/register_write_logger.hpp \ chip/ymfm/ymfm.h \ chip/ymfm/ymfm_2608.hpp \ chip/ymfm/ymfm_adpcm.h \ chip/ymfm/ymfm_fm.h \ chip/ymfm/ymfm_fm.ipp \ chip/ymfm/ymfm_opn.h \ chip/ymfm/ymfm_ssg.h \ command/command_id.hpp \ command/instrument/swap_instruments_command.hpp \ command/pattern/change_values_in_pattern_command.hpp \ command/pattern/paste_insert_copied_data_to_pattern_command.hpp \ command/pattern/pattern_command_utils.hpp \ command/pattern/set_key_cut_to_step_command.hpp \ command/pattern/transpose_note_in_pattern_command.hpp \ echo_buffer.hpp \ enum_hash.hpp \ gui/bookmark_manager_form.hpp \ gui/command/instrument/instrument_command_qt_utils.hpp \ gui/command/instrument/instrument_commands_qt.hpp \ gui/command/instrument/swap_instruments_qt_command.hpp \ gui/command/order/order_commands_qt.hpp \ gui/command/order/order_list_common_qt_command.hpp \ gui/command/pattern/pattern_editor_common_qt_command.hpp \ gui/command_result_message_box.hpp \ gui/dpi.hpp \ gui/drop_detect_list_widget.hpp \ gui/effect_description.hpp \ gui/effect_list_dialog.hpp \ gui/file_io_error_message_box.hpp \ gui/font_info_widget.hpp \ gui/go_to_dialog.hpp \ gui/gui_utils.hpp \ gui/hide_tracks_dialog.hpp \ gui/instrument_editor/adpcm_address_spin_box.hpp \ gui/instrument_editor/adpcm_drumkit_editor.hpp \ gui/instrument_editor/adpcm_instrument_editor.hpp \ gui/instrument_editor/adpcm_sample_editor.hpp \ gui/instrument_editor/arpeggio_macro_editor.hpp \ gui/instrument_editor/fm_instrument_editor.hpp \ gui/instrument_editor/grid_settings_dialog.hpp \ gui/instrument_editor/instrument_editor.hpp \ gui/instrument_editor/instrument_editor_manager.hpp \ gui/instrument_editor/instrument_editor_utils.hpp \ gui/instrument_editor/pan_macro_editor.hpp \ gui/instrument_editor/sample_length_dialog.hpp \ gui/instrument_editor/ssg_instrument_editor.hpp \ gui/instrument_editor/tone_noise_macro_editor.hpp \ gui/jam_layout.hpp \ gui/key_signature_manager_form.hpp \ gui/keyboard_shortcut_list_dialog.hpp \ gui/mainwindow.hpp \ chip/nuked/ym3438.h \ chip/chip.hpp \ chip/opna.hpp \ chip/resampler.hpp \ bamboo_tracker.hpp \ gui/note_name_manager.hpp \ gui/swap_tracks_dialog.hpp \ gui/transpose_song_dialog.hpp \ gui/wheel_spin_box.hpp \ instrument/instrument_property_defs.hpp \ instrument/sample_adpcm.hpp \ instrument/sample_repeat.hpp \ instrument/sequence_property.hpp \ io/btb_io.hpp \ io/bti_io.hpp \ io/btm_io.hpp \ io/dat_io.hpp \ io/dmp_io.hpp \ io/export_io.hpp \ io/ff_io.hpp \ io/ins_io.hpp \ io/io_file_type.hpp \ io/io_utils.hpp \ io/opni_io.hpp \ io/p86_io.hpp \ io/pmb_io.hpp \ io/ppc_io.hpp \ io/pps_io.hpp \ io/pvi_io.hpp \ io/pzi_io.hpp \ io/raw_adpcm_io.hpp \ io/tfi_io.hpp \ io/vgi_io.hpp \ io/wav_container.hpp \ io/wopn_io.hpp \ io/y12_io.hpp \ jamming.hpp \ module/effect.hpp \ note.hpp \ playback.hpp \ song_length_calculator.hpp \ audio/audio_stream.hpp \ instrument/instruments_manager.hpp \ command/command_manager.hpp \ command/instrument/add_instrument_command.hpp \ command/instrument/remove_instrument_command.hpp \ command/commands.hpp \ gui/command/instrument/add_instrument_qt_command.hpp \ gui/command/instrument/remove_instrument_qt_command.hpp \ gui/instrument_editor/fm_operator_table.hpp \ gui/labeled_vertical_slider.hpp \ gui/labeled_horizontal_slider.hpp \ gui/slider_style.hpp \ gui/command/instrument/change_instrument_name_qt_command.hpp \ command/instrument/change_instrument_name_command.hpp \ opna_controller.hpp \ instrument/instrument.hpp \ instrument/envelope_fm.hpp \ gui/event_guard.hpp \ audio/audio_stream_rtaudio.hpp \ tick_counter.hpp \ module/module.hpp \ module/song.hpp \ module/pattern.hpp \ module/track.hpp \ module/step.hpp \ gui/order_list_editor/order_list_panel.hpp \ gui/order_list_editor/order_list_editor.hpp \ gui/pattern_editor/pattern_editor_panel.hpp \ gui/pattern_editor/pattern_editor.hpp \ command/pattern/set_key_off_to_step_command.hpp \ gui/command/pattern/pattern_commands_qt.hpp \ command/pattern/set_key_on_to_step_command.hpp \ gui/pattern_editor/pattern_position.hpp \ command/pattern/set_instrument_to_step_command.hpp \ command/pattern/erase_instrument_in_step_command.hpp \ command/pattern/set_volume_to_step_command.hpp \ command/pattern/erase_volume_in_step_command.hpp \ command/pattern/set_effect_id_to_step_command.hpp \ command/pattern/erase_effect_in_step_command.hpp \ command/pattern/set_effect_value_to_step_command.hpp \ command/pattern/erase_effect_value_in_step_command.hpp \ command/pattern/insert_step_command.hpp \ command/pattern/delete_previous_step_command.hpp \ command/pattern/erase_step_command.hpp \ gui/command/instrument/deep_clone_instrument_qt_command.hpp \ command/instrument/deep_clone_instrument_command.hpp \ command/instrument/clone_instrument_command.hpp \ gui/command/instrument/clone_instrument_qt_command.hpp \ gui/order_list_editor/order_position.hpp \ command/order/set_pattern_to_order_command.hpp \ command/order/insert_order_below_command.hpp \ command/order/delete_order_command.hpp \ command/pattern/paste_copied_data_to_pattern_command.hpp \ command/pattern/erase_cells_in_pattern_command.hpp \ command/order/paste_copied_data_to_order_command.hpp \ instrument/lfo_fm.hpp \ gui/instrument_editor/visualized_instrument_macro_editor.hpp \ instrument/sequence_iterator_interface.hpp \ instrument/effect_iterator.hpp \ command/pattern/paste_mix_copied_data_to_pattern_command.hpp \ gui/module_properties_dialog.hpp \ gui/groove_settings_dialog.hpp \ gui/configuration_dialog.hpp \ command/pattern/expand_pattern_command.hpp \ command/pattern/shrink_pattern_command.hpp \ command/abstract_command.hpp \ instrument/abstract_instrument_property.hpp \ command/order/duplicate_order_command.hpp \ command/order/move_order_command.hpp \ command/order/clone_patterns_command.hpp \ command/order/clone_order_command.hpp \ command/pattern/set_echo_buffer_access_command.hpp \ gui/comment_edit_dialog.hpp \ io/binary_container.hpp \ utils.hpp \ vector_2d.hpp \ version.hpp \ command/pattern/interpolate_pattern_command.hpp \ command/pattern/reverse_pattern_command.hpp \ command/pattern/replace_instrument_in_pattern_command.hpp \ gui/vgm_export_settings_dialog.hpp \ gui/wave_export_settings_dialog.hpp \ configuration.hpp \ gui/configuration_handler.hpp \ gui/color_palette.hpp \ command/pattern/paste_overwrite_copied_data_to_pattern_command.hpp \ io/file_io_error.hpp \ format/wopn_file.h \ instrument/bank.hpp \ gui/instrument_selection_dialog.hpp \ gui/s98_export_settings_dialog.hpp \ precise_timer.hpp \ io/module_io.hpp \ io/instrument_io.hpp \ io/bank_io.hpp \ gui/fm_envelope_set_edit_dialog.hpp \ gui/file_history.hpp \ midi/midi.hpp \ gui/q_application_wrapper.hpp \ gui/wave_visual.hpp FORMS += \ gui/bookmark_manager_form.ui \ gui/effect_list_dialog.ui \ gui/font_info_widget.ui \ gui/go_to_dialog.ui \ gui/hide_tracks_dialog.ui \ gui/instrument_editor/adpcm_drumkit_editor.ui \ gui/instrument_editor/adpcm_instrument_editor.ui \ gui/instrument_editor/adpcm_sample_editor.ui \ gui/instrument_editor/fm_instrument_editor.ui \ gui/instrument_editor/grid_settings_dialog.ui \ gui/instrument_editor/sample_length_dialog.ui \ gui/instrument_editor/ssg_instrument_editor.ui \ gui/key_signature_manager_form.ui \ gui/keyboard_shortcut_list_dialog.ui \ gui/mainwindow.ui \ gui/instrument_editor/fm_operator_table.ui \ gui/labeled_vertical_slider.ui \ gui/labeled_horizontal_slider.ui \ gui/order_list_editor/order_list_editor.ui \ gui/pattern_editor/pattern_editor.ui \ gui/instrument_editor/visualized_instrument_macro_editor.ui \ gui/module_properties_dialog.ui \ gui/groove_settings_dialog.ui \ gui/configuration_dialog.ui \ gui/comment_edit_dialog.ui \ gui/swap_tracks_dialog.ui \ gui/transpose_song_dialog.ui \ gui/vgm_export_settings_dialog.ui \ gui/wave_export_settings_dialog.ui \ gui/instrument_selection_dialog.ui \ gui/s98_export_settings_dialog.ui \ gui/fm_envelope_set_edit_dialog.ui INCLUDEPATH += \ $$PWD/instrument \ $$PWD/module # In-app resource bundle. Needs to be handled here because it generates an object file to link against include("resources/resources.pri") # App translations. lupdate requires the source code for updating these to work include("lang/lang.pri") CONFIG += link_prl system_* { CONFIG += link_pkgconfig } INCLUDEPATH += $$PWD/../submodules/emu2149/src LIBS += -L$$OUT_PWD/../submodules/emu2149 CONFIG(debug, debug|release):LIBS += -lemu2149d else:CONFIG(release, debug|release):LIBS += -lemu2149 system_rtaudio { PKGCONFIG += rtaudio } else { INCLUDEPATH += $$PWD/../submodules/RtAudio/src LIBS += -L$$OUT_PWD/../submodules/RtAudio CONFIG(debug, debug|release):LIBS += -lrtaudiod else:CONFIG(release, debug|release):LIBS += -lrtaudio } system_rtmidi { PKGCONFIG += rtmidi } else { INCLUDEPATH += $$PWD/../submodules/RtMidi/src LIBS += -L$$OUT_PWD/../submodules/RtMidi CONFIG(debug, debug|release):LIBS += -lrtmidid else:CONFIG(release, debug|release):LIBS += -lrtmidi } win32:CONFIG += real_chip real_chip { DEFINES += USE_REAL_CHIP SOURCES += \ chip/c86ctl/c86ctl_wrapper.cpp \ chip/scci/scci_wrapper.cpp HEADERS += \ chip/c86ctl/c86ctl.h \ chip/c86ctl/c86ctl_wrapper.hpp \ chip/scci/SCCIDefines.hpp \ chip/scci/scci.hpp \ chip/scci/scci_wrapper.hpp } BambooTracker-0.6.5/BambooTracker/CMakeLists.txt000066400000000000000000000273451476276175200215440ustar00rootroot00000000000000# C11 required set (CMAKE_C_STANDARD 11) set (CMAKE_C_STANDARD_REQUIRED ON) # C++17 required set (CMAKE_CXX_STANDARD 17) set (CMAKE_CXX_STANDARD_REQUIRED ON) set (BT_WARNFLAGS) if (MSVC) list (APPEND BT_WARNFLAGS /W3 /utf-8 /D_CRT_SECURE_NO_WARNINGS) else() list (APPEND BT_WARNFLAGS -Wall -Wextra -pedantic) endif() option (WARNINGS_ARE_ERRORS "Treat any warnings in BambooTracker's code as errors" ON) if (WARNINGS_ARE_ERRORS) if (MSVC) list (APPEND BT_WARNFLAGS /WX) else() list (APPEND BT_WARNFLAGS -Werror -pedantic-errors) endif() endif() set (CMAKE_AUTOMOC ON) set (CMAKE_AUTORCC ON) set (CMAKE_AUTOUIC ON) # Identify Qt version we're using message (STATUS "Attempting to identify Qt version.") find_package (Qt6 COMPONENTS Core) if (Qt6_FOUND) set (QT_VERSION 6) else() find_package (Qt5 COMPONENTS Core) if (Qt5_FOUND) set (QT_VERSION 5) else() message (FATAL_ERROR "Unable to locate either Qt5 or Qt6!") endif() endif() message(STATUS "Found Qt${QT_VERSION}") set (QT_COMPONENTS Core Gui Widgets LinguistTools) if (QT_VERSION EQUAL 6) list (APPEND QT_COMPONENTS Core5Compat) endif (QT_VERSION EQUAL 6) find_package ("Qt${QT_VERSION}" COMPONENTS ${QT_COMPONENTS} REQUIRED) # C/C++ & qrc Qt Resource files set (BT_SOURCES audio/audio_stream.cpp audio/audio_stream_rtaudio.cpp bamboo_tracker.cpp chip/blip_buf/blip_buf.c chip/chip.cpp chip/mame/fmopn.c chip/mame/mame_2608.cpp chip/mame/ymdeltat.c chip/nuked/nuked_2608.cpp chip/nuked/ym3438.c chip/opna.cpp chip/register_write_logger.cpp chip/ymfm/ymfm_2608.cpp chip/ymfm/ymfm_adpcm.cpp chip/ymfm/ymfm_opn.cpp chip/ymfm/ymfm_ssg.cpp chip/resampler.cpp command/command_manager.cpp command/instrument/add_instrument_command.cpp command/instrument/change_instrument_name_command.cpp command/instrument/clone_instrument_command.cpp command/instrument/deep_clone_instrument_command.cpp command/instrument/remove_instrument_command.cpp command/instrument/swap_instruments_command.cpp command/order/clone_order_command.cpp command/order/clone_patterns_command.cpp command/order/delete_order_command.cpp command/order/duplicate_order_command.cpp command/order/insert_order_below_command.cpp command/order/move_order_command.cpp command/order/paste_copied_data_to_order_command.cpp command/order/set_pattern_to_order_command.cpp command/pattern/change_values_in_pattern_command.cpp command/pattern/delete_previous_step_command.cpp command/pattern/erase_cells_in_pattern_command.cpp command/pattern/erase_effect_in_step_command.cpp command/pattern/erase_effect_value_in_step_command.cpp command/pattern/erase_instrument_in_step_command.cpp command/pattern/erase_step_command.cpp command/pattern/erase_volume_in_step_command.cpp command/pattern/expand_pattern_command.cpp command/pattern/insert_step_command.cpp command/pattern/interpolate_pattern_command.cpp command/pattern/paste_copied_data_to_pattern_command.cpp command/pattern/paste_insert_copied_data_to_pattern_command.cpp command/pattern/paste_mix_copied_data_to_pattern_command.cpp command/pattern/paste_overwrite_copied_data_to_pattern_command.cpp command/pattern/pattern_command_utils.cpp command/pattern/replace_instrument_in_pattern_command.cpp command/pattern/reverse_pattern_command.cpp command/pattern/set_echo_buffer_access_command.cpp command/pattern/set_effect_id_to_step_command.cpp command/pattern/set_effect_value_to_step_command.cpp command/pattern/set_instrument_to_step_command.cpp command/pattern/set_key_cut_to_step_command.cpp command/pattern/set_key_off_to_step_command.cpp command/pattern/set_key_on_to_step_command.cpp command/pattern/set_volume_to_step_command.cpp command/pattern/shrink_pattern_command.cpp command/pattern/transpose_note_in_pattern_command.cpp configuration.cpp format/wopn_file.c gui/bookmark_manager_form.cpp gui/color_palette.cpp gui/command/instrument/add_instrument_qt_command.cpp gui/command/instrument/change_instrument_name_qt_command.cpp gui/command/instrument/clone_instrument_qt_command.cpp gui/command/instrument/deep_clone_instrument_qt_command.cpp gui/command/instrument/instrument_command_qt_utils.cpp gui/command/instrument/remove_instrument_qt_command.cpp gui/command/instrument/swap_instruments_qt_command.cpp gui/command/order/order_list_common_qt_command.cpp gui/command/pattern/pattern_editor_common_qt_command.cpp gui/comment_edit_dialog.cpp gui/configuration_dialog.cpp gui/configuration_handler.cpp gui/drop_detect_list_widget.cpp gui/effect_description.cpp gui/effect_list_dialog.cpp gui/event_guard.cpp gui/file_history.cpp gui/file_io_error_message_box.cpp gui/font_info_widget.cpp gui/fm_envelope_set_edit_dialog.cpp gui/go_to_dialog.cpp gui/groove_settings_dialog.cpp gui/gui_utils.cpp gui/hide_tracks_dialog.cpp gui/instrument_editor/adpcm_address_spin_box.cpp gui/instrument_editor/adpcm_drumkit_editor.cpp gui/instrument_editor/adpcm_instrument_editor.cpp gui/instrument_editor/adpcm_sample_editor.cpp gui/instrument_editor/arpeggio_macro_editor.cpp gui/instrument_editor/fm_instrument_editor.cpp gui/instrument_editor/fm_operator_table.cpp gui/instrument_editor/grid_settings_dialog.cpp gui/instrument_editor/instrument_editor.cpp gui/instrument_editor/instrument_editor_manager.cpp gui/instrument_editor/instrument_editor_utils.cpp gui/instrument_editor/pan_macro_editor.cpp gui/instrument_editor/sample_length_dialog.cpp gui/instrument_editor/ssg_instrument_editor.cpp gui/instrument_editor/tone_noise_macro_editor.cpp gui/instrument_editor/visualized_instrument_macro_editor.cpp gui/instrument_selection_dialog.cpp gui/keyboard_shortcut_list_dialog.cpp gui/key_signature_manager_form.cpp gui/labeled_horizontal_slider.cpp gui/labeled_vertical_slider.cpp gui/mainwindow.cpp gui/module_properties_dialog.cpp gui/note_name_manager.cpp gui/order_list_editor/order_list_editor.cpp gui/order_list_editor/order_list_panel.cpp gui/pattern_editor/pattern_editor.cpp gui/pattern_editor/pattern_editor_panel.cpp gui/q_application_wrapper.cpp gui/s98_export_settings_dialog.cpp gui/slider_style.cpp gui/swap_tracks_dialog.cpp gui/transpose_song_dialog.cpp gui/vgm_export_settings_dialog.cpp gui/wave_export_settings_dialog.cpp gui/wave_visual.cpp gui/wheel_spin_box.cpp instrument/abstract_instrument_property.cpp instrument/bank.cpp instrument/effect_iterator.cpp instrument/envelope_fm.cpp instrument/instrument.cpp instrument/instruments_manager.cpp instrument/lfo_fm.cpp instrument/sample_adpcm.cpp instrument/sequence_property.cpp io/bank_io.cpp io/binary_container.cpp io/btb_io.cpp io/bti_io.cpp io/btm_io.cpp io/dat_io.cpp io/dmp_io.cpp io/export_io.cpp io/ff_io.cpp io/instrument_io.cpp io/ins_io.cpp io/io_utils.cpp io/module_io.cpp io/opni_io.cpp io/p86_io.cpp io/pmb_io.cpp io/ppc_io.cpp io/pps_io.cpp io/pvi_io.cpp io/pzi_io.cpp io/raw_adpcm_io.cpp io/tfi_io.cpp io/vgi_io.cpp io/wav_container.cpp io/wopn_io.cpp io/y12_io.cpp jamming.cpp main.cpp midi/midi.cpp module/effect.cpp module/module.cpp module/pattern.cpp module/song.cpp module/step.cpp module/track.cpp note.cpp opna_controller.cpp playback.cpp precise_timer.cpp song_length_calculator.cpp tick_counter.cpp resources/doc/doc.qrc resources/icon/icon.qrc ) # UI forms set (BT_FORMS gui/bookmark_manager_form.ui gui/comment_edit_dialog.ui gui/configuration_dialog.ui gui/effect_list_dialog.ui gui/font_info_widget.ui gui/fm_envelope_set_edit_dialog.ui gui/go_to_dialog.ui gui/groove_settings_dialog.ui gui/hide_tracks_dialog.ui gui/instrument_editor/adpcm_drumkit_editor.ui gui/instrument_editor/adpcm_instrument_editor.ui gui/instrument_editor/adpcm_sample_editor.ui gui/instrument_editor/fm_instrument_editor.ui gui/instrument_editor/fm_operator_table.ui gui/instrument_editor/grid_settings_dialog.ui gui/instrument_editor/sample_length_dialog.ui gui/instrument_editor/ssg_instrument_editor.ui gui/instrument_editor/visualized_instrument_macro_editor.ui gui/instrument_selection_dialog.ui gui/keyboard_shortcut_list_dialog.ui gui/key_signature_manager_form.ui gui/labeled_horizontal_slider.ui gui/labeled_vertical_slider.ui gui/mainwindow.ui gui/module_properties_dialog.ui gui/order_list_editor/order_list_editor.ui gui/pattern_editor/pattern_editor.ui gui/s98_export_settings_dialog.ui gui/swap_tracks_dialog.ui gui/transpose_song_dialog.ui gui/vgm_export_settings_dialog.ui gui/wave_export_settings_dialog.ui ) set (BT_INCLUDEPATHS "${CMAKE_CURRENT_SOURCE_DIR}" instrument module ) option (REAL_CHIP "Compile with support for SCCI and C86CTL interfaces to a real OPNA chip" ${WIN32}) if (REAL_CHIP) list (APPEND BT_SOURCES chip/c86ctl/c86ctl_wrapper.cpp chip/scci/scci_wrapper.cpp ) list (APPEND BT_INCLUDEPATHS chip/c86ctl chip/scci ) endif (REAL_CHIP) if (APPLE) set (MACOSX_BUNDLE_ICON_FILE BambooTracker.icns) set (MACOSX_ICNS "${CMAKE_CURRENT_SOURCE_DIR}/resources/icon/${MACOSX_BUNDLE_ICON_FILE}") set_source_files_properties (${MACOSX_ICNS} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources" ) endif() add_executable (BambooTracker WIN32 MACOSX_BUNDLE ${BT_SOURCES} ${BT_FORMS} ${MACOSX_ICNS} ) # CMake Qt lacks a publicly-supported way of generating the data required for setting the Windows app icon at build time, unlike QMake. Use an internal Qt function. # https://bugreports.qt.io/browse/QTBUG-87618 # https://gitlab.kitware.com/cmake/cmake/-/issues/21314 if (WIN32 AND QT_VERSION EQUAL 6) set_target_properties (BambooTracker PROPERTIES QT_TARGET_VERSION "${PROJECT_VERSION}" QT_TARGET_COMPANY_NAME "${PROJECT_NAME}" QT_TARGET_DESCRIPTION "YM2608 music tracker" QT_TARGET_COPYRIGHT "Copyright (C) Rerrah and other BambooTracker contributors" QT_TARGET_PRODUCT_NAME "${PROJECT_NAME}" QT_TARGET_RC_ICONS "${CMAKE_CURRENT_SOURCE_DIR}/resources/icon/BambooTracker.ico" ) # In case this ever gets changed / breaks, only try it if there's some chance of it working & allow opt-out set (GENERATE_WIN32_RC_DEFAULT OFF) if (COMMAND _qt_internal_generate_win32_rc_file) set (GENERATE_WIN32_RC_DEFAULT ON) endif() option (GENERATE_WIN32_RC "Use a Qt6-internal function to generate a Win32 RC file for the project. This is needed to get an app icon when using CMake." ${GENERATE_WIN32_RC_DEFAULT}) if (GENERATE_WIN32_RC) _qt_internal_generate_win32_rc_file (BambooTracker) endif() endif() target_include_directories (BambooTracker PRIVATE ${BT_INCLUDEPATHS}) target_compile_options (BambooTracker PRIVATE ${BT_WARNFLAGS}) if (REAL_CHIP) target_compile_definitions (BambooTracker PRIVATE USE_REAL_CHIP) endif (REAL_CHIP) if (QT_VERSION EQUAL 6) set (QT_LIBRARIES Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Core5Compat) else() set (QT_LIBRARIES Qt5::Core Qt5::Gui Qt5::Widgets) endif() target_link_libraries (BambooTracker PUBLIC ${QT_LIBRARIES}) # Dependencies set (THREADS_PREFER_PTHREAD_FLAG ON) include (FindThreads REQUIRED) target_include_directories (BambooTracker SYSTEM PRIVATE ${EMU2149_INCLUDE_DIRS} ${RTAUDIO_INCLUDE_DIRS} ${RTMIDI_INCLUDE_DIRS}) target_compile_options (BambooTracker PRIVATE ${EMU2149_COMPILE_OPTIONS} ${RTAUDIO_COMPILE_OPTIONS} ${RTMIDI_COMPILE_OPTIONS}) if ("${CMAKE_VERSION}" VERSION_LESS "3.13") message (WARNING "CMake version is <3.13, using old pkg-config LDFLAGS. " "You may encounter linking problems with these!" ) target_link_libraries (BambooTracker PRIVATE ${EMU2149_LDFLAGS_LEGACY} ${RTAUDIO_LDFLAGS_LEGACY} ${RTMIDI_LDFLAGS_LEGACY} Threads::Threads) else() target_link_libraries (BambooTracker PRIVATE ${EMU2149_LIBRARIES} ${RTAUDIO_LIBRARIES} ${RTMIDI_LIBRARIES} Threads::Threads) target_link_directories (BambooTracker PRIVATE ${EMU2149_LINK_DIRS} ${RTAUDIO_LINK_DIRS} ${RTMIDI_LINK_DIRS}) target_link_options (BambooTracker PRIVATE ${EMU2149_LINK_OPTIONS} ${RTAUDIO_LINK_OPTIONS} ${RTMIDI_LINK_OPTIONS}) endif() install (TARGETS BambooTracker DESTINATION "${CMAKE_INSTALL_BINDIR}") add_subdirectory (lang) BambooTracker-0.6.5/BambooTracker/audio/000077500000000000000000000000001476276175200200725ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/audio/audio_stream.cpp000066400000000000000000000075531476276175200232640ustar00rootroot00000000000000/* * Copyright (C) 2018-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "audio_stream.hpp" #include const std::string AudioStream::AUDIO_OUT_CLIENT_NAME = "BambooTracker"; AudioStream::AudioStream(QObject *parent) : QObject(parent), rate_(0), intrRate_(0), intrCount_(0), intrCountRest_(0), gcb_(nullptr), gcbPtr_(nullptr), tuState_(-1), started_(false), quitNotify_(false), tickNotifier_([this]() { tickNotifierRun(); }) { } AudioStream::~AudioStream() { quitNotify_.store(true); tickNotifierSem_.release(); tickNotifier_.join(); } void AudioStream::setGenerateCallback(GenerateCallback* cb, void* cbPtr) { std::lock_guard lock(mutex_); gcb_ = cb; gcbPtr_ = cbPtr; } void AudioStream::setTickUpdateCallback(TickUpdateCallback* cb, void* cbPtr) { std::lock_guard lock(mutex_); tucb_ = cb; tucbPtr_ = cbPtr; } bool AudioStream::initialize(uint32_t rate, uint32_t duration, uint32_t intrRate, const QString& backend, const QString& device, QString* errDetail) { Q_UNUSED(duration) Q_UNUSED(backend) Q_UNUSED(device) Q_UNUSED(errDetail) started_ = false; rate_ = rate; setInterruption(intrRate); return true; } void AudioStream::setInterruption(uint32_t intrRate) { std::lock_guard lock(mutex_); intrRate_ = intrRate; intrCount_ = rate_ / intrRate_; } uint32_t AudioStream::getStreamRate() const noexcept { return rate_; } void AudioStream::start() { std::lock_guard lock(mutex_); started_ = true; } void AudioStream::stop() { std::lock_guard lock(mutex_); started_ = false; } bool AudioStream::generate(int16_t* container, uint32_t nSamples) { GenerateCallback* gcb = nullptr; void* gcbPtr = nullptr; TickUpdateCallback* tucb = nullptr; bool started = false; std::unique_lock lock(mutex_, std::try_to_lock); if (lock.owns_lock()) { gcb = gcb_; gcbPtr = gcbPtr_; tucb = tucb_; started = started_; } if (!gcb || !tucb || !started) { std::fill(container, container + (nSamples << 1), 0); return true; } int16_t* destPtr = container; while (nSamples) { if (!intrCountRest_) { // Interruption intrCountRest_ = intrCount_; // Set counts to next interruption generateTick(); } size_t count = std::min(intrCountRest_, nSamples); nSamples -= count; intrCountRest_ -= count; bool result = gcb(destPtr, count, gcbPtr); if (!result) { // Something went wrong in sample generation callback emit streamErrorInCallback(QVariant()); return false; } destPtr += (count << 1); // Move head } return true; } void AudioStream::generateTick() { tuState_.store(tucb_(tucbPtr_)); tickNotifierSem_.release(); } void AudioStream::tickNotifierRun() { while (true) { tickNotifierSem_.acquire(); if (quitNotify_.load()) return; emit streamInterrupted(tuState_.load()); } } BambooTracker-0.6.5/BambooTracker/audio/audio_stream.hpp000066400000000000000000000057241476276175200232670ustar00rootroot00000000000000/* * Copyright (C) 2018-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include #include #include #include #include #include class AudioStream : public QObject { Q_OBJECT public: explicit AudioStream(QObject* parent = nullptr); virtual ~AudioStream() override; using GenerateCallback = bool (int16_t*, size_t, void*); void setGenerateCallback(GenerateCallback* cb, void* cbPtr); using TickUpdateCallback = int (void*); void setTickUpdateCallback(TickUpdateCallback* cb, void* cbPtr); // duration: miliseconds virtual bool initialize(uint32_t rate, uint32_t duration, uint32_t intrRate, const QString& backend, const QString& device, QString* errDetail = nullptr); virtual void shutdown() = 0; virtual std::vector getAvailableBackends() const = 0; virtual QString getCurrentBackend() const = 0; virtual std::vector getAvailableDevices() const = 0; virtual std::vector getAvailableDevices(const QString& backend) const = 0; virtual QString getDefaultOutputDevice() const = 0; virtual QString getDefaultOutputDevice(const QString& backend) const = 0; void setInterruption(uint32_t inrtRate); uint32_t getStreamRate() const noexcept; virtual void start(); virtual void stop(); signals: void streamInterrupted(int state); void streamErrorInCallback(const QVariant& data); protected: static const std::string AUDIO_OUT_CLIENT_NAME; bool generate(int16_t* container, uint32_t nSamples); private: uint32_t rate_; uint32_t intrRate_; uint32_t intrCount_; uint32_t intrCountRest_; std::mutex mutex_; GenerateCallback* gcb_; void* gcbPtr_; TickUpdateCallback* tucb_; void* tucbPtr_; std::atomic_int tuState_; bool started_; std::atomic_bool quitNotify_; QSemaphore tickNotifierSem_; std::thread tickNotifier_; void generateTick(); void tickNotifierRun(); }; BambooTracker-0.6.5/BambooTracker/audio/audio_stream_rtaudio.cpp000066400000000000000000000134571476276175200250130ustar00rootroot00000000000000/* * Copyright (C) 2019-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "audio_stream_rtaudio.hpp" #include #include #include #include "RtAudio.h" AudioStreamRtAudio::AudioStreamRtAudio(QObject* parent) : AudioStream(parent) { audio_.reset(new RtAudio); } AudioStreamRtAudio::~AudioStreamRtAudio() = default; bool AudioStreamRtAudio::initialize(uint32_t rate, uint32_t duration, uint32_t intrRate, const QString& backend, const QString& device, QString* errDetail) { shutdown(); setBackend(backend); RtAudio* audio = audio_.get(); const std::string deviceUtf8 = device.toStdString(); RtAudio::StreamParameters param; param.nChannels = 2; param.deviceId = ~0u; for (unsigned int id : audio->getDeviceIds()) { RtAudio::DeviceInfo info = audio->getDeviceInfo(id); if (info.outputChannels >= 2 && info.name == deviceUtf8) param.deviceId = id; } if (param.deviceId == ~0u) param.deviceId = audio->getDefaultOutputDevice(); RtAudio::StreamOptions opts; opts.flags = RTAUDIO_SCHEDULE_REALTIME; opts.streamName = AUDIO_OUT_CLIENT_NAME; opts.numberOfBuffers = 2; auto callback = +[](void* outputBuffer, void*, unsigned int nFrames, double, RtAudioStreamStatus, void* userData) -> int { auto stream = reinterpret_cast(userData); bool result = stream->generate(static_cast(outputBuffer), nFrames); return result ? 0 : 1; }; unsigned int bufferSize = rate * duration / 1000; bool isSuccessed = false; const RtAudioErrorType errorType = audio->openStream(¶m, nullptr, RTAUDIO_SINT16, rate, &bufferSize, callback, this, &opts); if (errorType == RtAudioErrorType::RTAUDIO_NO_ERROR) { if (errDetail) *errDetail = ""; isSuccessed = true; rate = audio->getStreamSampleRate(); // Match to real rate (for ALSA) } else { if (errDetail) *errDetail = QString::fromStdString(audio->getErrorText()); } AudioStream::initialize(rate, duration, intrRate, backend, device); return isSuccessed; } void AudioStreamRtAudio::shutdown() { if (audio_->isStreamOpen()) audio_->closeStream(); } void AudioStreamRtAudio::setBackend(const QString& backend) { std::vector apis; RtAudio::getCompiledApi(apis); size_t i = 0; for (const auto& api : apis) { if (backend == QString::fromStdString(RtAudio::getApiDisplayName(api))) { audio_.reset(new RtAudio(apis[i])); return; } ++i; } audio_.reset(new RtAudio); } std::vector AudioStreamRtAudio::getAvailableBackends() const { std::vector apis; std::vector names; RtAudio::getCompiledApi(apis); for (const auto& api : apis) names.push_back(QString::fromStdString(RtAudio::getApiDisplayName(api))); return names; } QString AudioStreamRtAudio::getCurrentBackend() const { return QString::fromStdString(RtAudio::getApiDisplayName(audio_->getCurrentApi())); } std::vector AudioStreamRtAudio::getAvailableDevices() const { RtAudio* audio = audio_.get(); std::vector devices; for (unsigned int id : audio->getDeviceIds()) { RtAudio::DeviceInfo info = audio->getDeviceInfo(id); if (info.outputChannels >= 2) devices.push_back(QString::fromStdString(info.name)); } return devices; } std::vector AudioStreamRtAudio::getAvailableDevices(const QString& backend) const { std::vector apis; RtAudio::getCompiledApi(apis); RtAudio::Api api = RtAudio::RTAUDIO_DUMMY; for (const auto& apiAvailable : apis) { if (backend == QString::fromStdString(RtAudio::getApiDisplayName(apiAvailable))) { api = apiAvailable; break; } } std::vector list; auto a = std::make_unique(api); list.reserve(a->getDeviceCount()); for (unsigned int id : a->getDeviceIds()) { RtAudio::DeviceInfo info = a->getDeviceInfo(id); if (info.outputChannels >= 2) list.push_back(QString::fromStdString(info.name)); } return list; } QString AudioStreamRtAudio::getDefaultOutputDevice() const { return QString::fromStdString(audio_->getDeviceInfo(audio_->getDefaultOutputDevice()).name); } QString AudioStreamRtAudio::getDefaultOutputDevice(const QString& backend) const { std::vector apis; RtAudio::getCompiledApi(apis); RtAudio::Api api = RtAudio::RTAUDIO_DUMMY; for (const auto& apiAvailable : apis) { if (backend == QString::fromStdString(RtAudio::getApiDisplayName(apiAvailable))) { api = apiAvailable; break; } } std::vector list; auto a = std::make_unique(api); return QString::fromStdString(a->getDeviceInfo(a->getDefaultOutputDevice()).name); } void AudioStreamRtAudio::start() { AudioStream::start(); if (audio_->isStreamOpen() && !audio_->isStreamRunning()) audio_->startStream(); } void AudioStreamRtAudio::stop() { AudioStream::stop(); if (audio_->isStreamOpen() && audio_->isStreamRunning()) audio_->stopStream(); } BambooTracker-0.6.5/BambooTracker/audio/audio_stream_rtaudio.hpp000066400000000000000000000040111476276175200250020ustar00rootroot00000000000000/* * Copyright (C) 2019-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "audio_stream.hpp" #include class RtAudio; class AudioStreamRtAudio final : public AudioStream { public: explicit AudioStreamRtAudio(QObject* parent = nullptr); ~AudioStreamRtAudio() override; bool initialize(uint32_t rate, uint32_t duration, uint32_t intrRate, const QString& backend, const QString& device, QString* errDetail) override; void shutdown() override; std::vector getAvailableBackends() const override; QString getCurrentBackend() const override; std::vector getAvailableDevices() const override; virtual std::vector getAvailableDevices(const QString& backend) const override; QString getDefaultOutputDevice() const override; QString getDefaultOutputDevice(const QString& backend) const override; void start() override; void stop() override; private: std::unique_ptr audio_; void setBackend(const QString& backend); }; BambooTracker-0.6.5/BambooTracker/bamboo_tracker.cpp000066400000000000000000002364151476276175200224620ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "bamboo_tracker.hpp" #include #include #include #include #include #include #include "configuration.hpp" #include "opna_controller.hpp" #include "playback.hpp" #include "tick_counter.hpp" #include "command/commands.hpp" #include "chip/register_write_logger.hpp" #include "io/module_io.hpp" #include "io/instrument_io.hpp" #include "io/bank_io.hpp" #include "bank.hpp" #include "note.hpp" #include "song_length_calculator.hpp" #include "utils.hpp" namespace { const uint32_t CHIP_CLOCK = 3993600 * 2; } BambooTracker::BambooTracker(std::weak_ptr config) : instMan_(std::make_shared(config.lock()->getOverwriteUnusedUneditedPropety())), jamMan_(std::make_unique()), tickCounter_(std::make_shared()), mod_(std::make_shared()), curOctave_(Note::DEFAULT_OCTAVE), curSongNum_(0), curTrackNum_(0), curOrderNum_(0), curStepNum_(0), curInstNum_(-1), curVolume_(127), mkOrder_(-1), mkStep_(-1), isFollowPlay_(true) { opnaCtrl_ = std::make_shared( static_cast(config.lock()->getEmulator()), CHIP_CLOCK, config.lock()->getSampleRate(), config.lock()->getBufferLength(), config.lock()->getResamplerType()); opnaCtrl_->setImmediateWriteMode(config.lock()->getImmediateWriteModeEnabled()); setMasterVolume(config.lock()->getMixerVolumeMaster()); setMasterVolumeFM(config.lock()->getMixerVolumeFM()); setMasterVolumeSSG(config.lock()->getMixerVolumeSSG()); songStyle_ = mod_->getSong(curSongNum_).getStyle(); playback_ = std::make_unique( opnaCtrl_, instMan_, tickCounter_, mod_, config.lock()->getRetrieveChannelState()); storeOnlyUsedSamples_ = config.lock()->getWriteOnlyUsedSamples(); volFMReversed_ = config.lock()->getReverseFMVolumeOrder(); makeNewModule(); } BambooTracker::~BambooTracker() = default; /********** Change configuration **********/ void BambooTracker::changeConfiguration(std::weak_ptr config) { setStreamRate(static_cast(config.lock()->getSampleRate())); setStreamDuration(static_cast(config.lock()->getBufferLength())); opnaCtrl_->setImmediateWriteMode(config.lock()->getImmediateWriteModeEnabled()); opnaCtrl_->setResampler(config.lock()->getResamplerType()); setMasterVolume(config.lock()->getMixerVolumeMaster()); if (mod_->getMixerType() == MixerType::UNSPECIFIED) { setMasterVolumeFM(config.lock()->getMixerVolumeFM()); setMasterVolumeSSG(config.lock()->getMixerVolumeSSG()); } playback_->setChannelRetrieving(config.lock()->getRetrieveChannelState()); instMan_->setPropertyFindMode(config.lock()->getOverwriteUnusedUneditedPropety()); storeOnlyUsedSamples_ = config.lock()->getWriteOnlyUsedSamples(); volFMReversed_ = config.lock()->getReverseFMVolumeOrder(); } /********** Current octave **********/ void BambooTracker::setCurrentOctave(int octave) { curOctave_ = octave; } int BambooTracker::getCurrentOctave() const { return curOctave_; } /********** Current volume **********/ void BambooTracker::setCurrentVolume(int volume) { curVolume_ = volume; } int BambooTracker::getCurrentVolume() const { return curVolume_; } /********** Current track **********/ void BambooTracker::setCurrentTrack(int num) { curTrackNum_ = num; } TrackAttribute BambooTracker::getCurrentTrackAttribute() const { return songStyle_.trackAttribs.at(static_cast(curTrackNum_)); } /********** Current instrument **********/ void BambooTracker::setCurrentInstrument(int n) { curInstNum_ = n; } int BambooTracker::getCurrentInstrumentNumber() const { return curInstNum_; } /********** Instrument edit **********/ void BambooTracker::addInstrument(int num, InstrumentType type, const std::string& name) { comMan_.invoke(std::make_unique(instMan_, num, type, name)); } void BambooTracker::removeInstrument(int num) { comMan_.invoke(std::make_unique(instMan_, num)); } std::unique_ptr BambooTracker::getInstrument(int num) { std::shared_ptr inst = instMan_->getInstrumentSharedPtr(num); if (inst == nullptr) return std::unique_ptr(); else return std::unique_ptr(inst->clone()); } void BambooTracker::cloneInstrument(int num, int refNum) { comMan_.invoke(std::make_unique(instMan_, num, refNum)); } void BambooTracker::deepCloneInstrument(int num, int refNum) { comMan_.invoke(std::make_unique(instMan_, num, refNum)); } void BambooTracker::swapInstruments(int a, int b, bool patternChange) { comMan_.invoke(std::make_unique(instMan_, mod_, a, b, curSongNum_, patternChange)); } void BambooTracker::loadInstrument(io::BinaryContainer& container, const std::string& path, int instNum) { AbstractInstrument* inst = io::InstrumentIO::getInstance().loadInstrument(container, path, instMan_, instNum); comMan_.invoke(std::make_unique(instMan_, std::unique_ptr(inst))); } void BambooTracker::saveInstrument(io::BinaryContainer& container, int instNum) { io::InstrumentIO::getInstance().saveInstrument(container, instMan_, instNum); } void BambooTracker::importInstrument(const AbstractBank &bank, size_t index, int instNum) { AbstractInstrument* inst = bank.loadInstrument(index, instMan_, instNum); comMan_.invoke(std::make_unique( instMan_, std::unique_ptr(inst))); } void BambooTracker::exportInstruments(io::BinaryContainer& container, const std::vector& instNums) { io::BankIO::getInstance().saveBank(container, instMan_, instNums); } int BambooTracker::findFirstFreeInstrumentNumber() const { return instMan_->findFirstFreeInstrument(); } void BambooTracker::setInstrumentName(int num, const std::string& name) { comMan_.invoke(std::make_unique(instMan_, num, name)); } void BambooTracker::clearAllInstrument() { instMan_->clearAll(); } std::vector BambooTracker::getInstrumentIndices() const { return instMan_->getInstrumentIndices(); } std::vector BambooTracker::getUnusedInstrumentIndices() const { std::vector instIdcs = instMan_->getInstrumentIndices(); std::set regdInsts = mod_->getRegisterdInstruments(); std::vector unused; std::set_difference(instIdcs.begin(), instIdcs.end(), regdInsts.begin(), regdInsts.end(), std::back_inserter(unused)); return unused; } void BambooTracker::clearUnusedInstrumentProperties() { instMan_->clearUnusedInstrumentProperties(); } std::vector BambooTracker::getInstrumentNames() const { return instMan_->getInstrumentNameList(); } //--- FM void BambooTracker::setEnvelopeFMParameter(int envNum, FMEnvelopeParameter param, int value) { instMan_->setEnvelopeFMParameter(envNum, param, value); opnaCtrl_->updateInstrumentFMEnvelopeParameter(envNum, param); } void BambooTracker::setEnvelopeFMOperatorEnable(int envNum, int opNum, bool enable) { instMan_->setEnvelopeFMOperatorEnabled(envNum, opNum, enable); opnaCtrl_->setInstrumentFMOperatorEnabled(envNum, opNum); } void BambooTracker::setInstrumentFMEnvelope(int instNum, int envNum) { instMan_->setInstrumentFMEnvelope(instNum, envNum); opnaCtrl_->updateInstrumentFM(instNum); } std::multiset BambooTracker::getEnvelopeFMUsers(int envNum) const { return instMan_->getEnvelopeFMUsers(envNum); } void BambooTracker::setLFOFMParameter(int lfoNum, FMLFOParameter param, int value) { instMan_->setLFOFMParameter(lfoNum, param, value); opnaCtrl_->updateInstrumentFMLFOParameter(lfoNum, param); } void BambooTracker::setInstrumentFMLFOEnabled(int instNum, bool enabled) { instMan_->setInstrumentFMLFOEnabled(instNum, enabled); opnaCtrl_->updateInstrumentFM(instNum); } void BambooTracker::setInstrumentFMLFO(int instNum, int lfoNum) { instMan_->setInstrumentFMLFO(instNum, lfoNum); opnaCtrl_->updateInstrumentFM(instNum); } std::multiset BambooTracker::getLFOFMUsers(int lfoNum) const { return instMan_->getLFOFMUsers(lfoNum); } void BambooTracker::addOperatorSequenceFMSequenceData(FMEnvelopeParameter param, int opSeqNum, int data) { instMan_->addOperatorSequenceFMSequenceData(param, opSeqNum, data); } void BambooTracker::removeOperatorSequenceFMSequenceData(FMEnvelopeParameter param, int opSeqNum) { instMan_->removeOperatorSequenceFMSequenceData(param, opSeqNum); } void BambooTracker::setOperatorSequenceFMSequenceData(FMEnvelopeParameter param, int opSeqNum, int cnt, int data) { instMan_->setOperatorSequenceFMSequenceData(param, opSeqNum, cnt, data); } void BambooTracker::addOperatorSequenceFMLoop(FMEnvelopeParameter param, int opSeqNum, const InstrumentSequenceLoop& loop) { instMan_->addOperatorSequenceFMLoop(param, opSeqNum, loop); } void BambooTracker::removeOperatorSequenceFMLoop(FMEnvelopeParameter param, int opSeqNum, int begin, int end) { instMan_->removeOperatorSequenceFMLoop(param, opSeqNum, begin, end); } void BambooTracker::changeOperatorSequenceFMLoop(FMEnvelopeParameter param, int opSeqNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { instMan_->changeOperatorSequenceFMLoop(param, opSeqNum, prevBegin, prevEnd, loop); } void BambooTracker::clearOperatorSequenceFMLoops(FMEnvelopeParameter param, int opSeqNum) { instMan_->clearOperatorSequenceFMLoops(param, opSeqNum); } void BambooTracker::setOperatorSequenceFMRelease(FMEnvelopeParameter param, int opSeqNum, const InstrumentSequenceRelease& release) { instMan_->setOperatorSequenceFMRelease(param, opSeqNum, release); } void BambooTracker::setInstrumentFMOperatorSequence(int instNum, FMEnvelopeParameter param, int opSeqNum) { instMan_->setInstrumentFMOperatorSequence(instNum, param, opSeqNum); opnaCtrl_->updateInstrumentFM(instNum); } void BambooTracker::setInstrumentFMOperatorSequenceEnabled(int instNum, FMEnvelopeParameter param, bool enabled) { instMan_->setInstrumentFMOperatorSequenceEnabled(instNum, param, enabled); opnaCtrl_->updateInstrumentFM(instNum); } std::multiset BambooTracker::getOperatorSequenceFMUsers(FMEnvelopeParameter param, int opSeqNum) const { return instMan_->getOperatorSequenceFMUsers(param, opSeqNum); } void BambooTracker::setArpeggioFMType(int arpNum, SequenceType type) { instMan_->setArpeggioFMType(arpNum, type); } void BambooTracker::addArpeggioFMSequenceData(int arpNum, int data) { instMan_->addArpeggioFMSequenceData(arpNum, data); } void BambooTracker::removeArpeggioFMSequenceData(int arpNum) { instMan_->removeArpeggioFMSequenceData(arpNum); } void BambooTracker::setArpeggioFMSequenceData(int arpNum, int cnt, int data) { instMan_->setArpeggioFMSequenceData(arpNum, cnt, data); } void BambooTracker::addArpeggioFMLoop(int arpNum, const InstrumentSequenceLoop& loop) { instMan_->addArpeggioFMLoop(arpNum, loop); } void BambooTracker::removeArpeggioFMLoop(int arpNum, int begin, int end) { instMan_->removeArpeggioFMLoop(arpNum, begin, end); } void BambooTracker::changeArpeggioFMLoop(int arpNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { instMan_->changeArpeggioFMLoop(arpNum, prevBegin, prevEnd, loop); } void BambooTracker::clearArpeggioFMLoops(int arpNum) { instMan_->clearArpeggioFMLoops(arpNum); } void BambooTracker::setArpeggioFMRelease(int arpNum, const InstrumentSequenceRelease& release) { instMan_->setArpeggioFMRelease(arpNum, release); } void BambooTracker::setInstrumentFMArpeggio(int instNum, FMOperatorType op, int arpNum) { instMan_->setInstrumentFMArpeggio(instNum, op, arpNum); opnaCtrl_->updateInstrumentFM(instNum); } void BambooTracker::setInstrumentFMArpeggioEnabled(int instNum, FMOperatorType op, bool enabled) { instMan_->setInstrumentFMArpeggioEnabled(instNum, op, enabled); opnaCtrl_->updateInstrumentFM(instNum); } std::multiset BambooTracker::getArpeggioFMUsers(int arpNum) const { return instMan_->getArpeggioFMUsers(arpNum); } void BambooTracker::setPitchFMType(int ptNum, SequenceType type) { instMan_->setPitchFMType(ptNum, type); } void BambooTracker::addPitchFMSequenceData(int ptNum, int data) { instMan_->addPitchFMSequenceData(ptNum, data); } void BambooTracker::removePitchFMSequenceData(int ptNum) { instMan_->removePitchFMSequenceData(ptNum); } void BambooTracker::setPitchFMSequenceData(int ptNum, int cnt, int data) { instMan_->setPitchFMSequenceData(ptNum, cnt, data); } void BambooTracker::addPitchFMLoop(int ptNum, const InstrumentSequenceLoop& loop) { instMan_->addPitchFMLoop(ptNum, loop); } void BambooTracker::removePitchFMLoop(int ptNum, int begin, int end) { instMan_->removePitchFMLoop(ptNum, begin, end); } void BambooTracker::changePitchFMLoop(int ptNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { instMan_->changePitchFMLoop(ptNum, prevBegin, prevEnd, loop); } void BambooTracker::clearPitchFMLoops(int ptNum) { instMan_->clearPitchFMLoops(ptNum); } void BambooTracker::setPitchFMRelease(int ptNum, const InstrumentSequenceRelease& release) { instMan_->setPitchFMRelease(ptNum, release); } void BambooTracker::setInstrumentFMPitch(int instNum, FMOperatorType op, int ptNum) { instMan_->setInstrumentFMPitch(instNum, op, ptNum); opnaCtrl_->updateInstrumentFM(instNum); } void BambooTracker::setInstrumentFMPitchEnabled(int instNum, FMOperatorType op, bool enabled) { instMan_->setInstrumentFMPitchEnabled(instNum, op, enabled); opnaCtrl_->updateInstrumentFM(instNum); } std::multiset BambooTracker::getPitchFMUsers(int ptNum) const { return instMan_->getPitchFMUsers(ptNum); } void BambooTracker::addPanFMSequenceData(int panNum, int data) { instMan_->addPanFMSequenceData(panNum, data); } void BambooTracker::removePanFMSequenceData(int panNum) { instMan_->removePanFMSequenceData(panNum); } void BambooTracker::setPanFMSequenceData(int panNum, int cnt, int data) { instMan_->setPanFMSequenceData(panNum, cnt, data); } void BambooTracker::addPanFMLoop(int panNum, const InstrumentSequenceLoop& loop) { instMan_->addPanFMLoop(panNum, loop); } void BambooTracker::removePanFMLoop(int panNum, int begin, int end) { instMan_->removePanFMLoop(panNum, begin, end); } void BambooTracker::changePanFMLoop(int panNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { instMan_->changePanFMLoop(panNum, prevBegin, prevEnd, loop); } void BambooTracker::clearPanFMLoops(int panNum) { instMan_->clearPanFMLoops(panNum); } void BambooTracker::setPanFMRelease(int panNum, const InstrumentSequenceRelease& release) { instMan_->setPanFMRelease(panNum, release); } void BambooTracker::setInstrumentFMPan(int instNum, int panNum) { instMan_->setInstrumentFMPan(instNum, panNum); opnaCtrl_->updateInstrumentFM(instNum); } void BambooTracker::setInstrumentFMPanEnabled(int instNum, bool enabled) { instMan_->setInstrumentFMPanEnabled(instNum, enabled); opnaCtrl_->updateInstrumentFM(instNum); } std::multiset BambooTracker::getPanFMUsers(int panNum) const { return instMan_->getPanFMUsers(panNum); } void BambooTracker::setInstrumentFMEnvelopeResetEnabled(int instNum, FMOperatorType op, bool enabled) { instMan_->setInstrumentFMEnvelopeResetEnabled(instNum, op, enabled); opnaCtrl_->updateInstrumentFM(instNum); } //--- SSG void BambooTracker::addWaveformSSGSequenceData(int wfNum, const SSGWaveformUnit& data) { instMan_->addWaveformSSGSequenceData(wfNum, data); } void BambooTracker::removeWaveformSSGSequenceData(int wfNum) { instMan_->removeWaveformSSGSequenceData(wfNum); } void BambooTracker::setWaveformSSGSequenceData(int wfNum, int cnt, const SSGWaveformUnit& data) { instMan_->setWaveformSSGSequenceData(wfNum, cnt, data); } void BambooTracker::addWaveformSSGLoop(int wfNum, const InstrumentSequenceLoop& loop) { instMan_->addWaveformSSGLoop(wfNum, loop); } void BambooTracker::removeWaveformSSGLoop(int wfNum, int begin, int end) { instMan_->removeWaveformSSGLoop(wfNum, begin, end); } void BambooTracker::changeWaveformSSGLoop(int wfNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { instMan_->changeWaveformSSGLoop(wfNum, prevBegin, prevEnd, loop); } void BambooTracker::clearWaveformSSGLoops(int wfNum) { instMan_->clearWaveformSSGLoops(wfNum); } void BambooTracker::setWaveformSSGRelease(int wfNum, const InstrumentSequenceRelease& release) { instMan_->setWaveformSSGRelease(wfNum, release); } void BambooTracker::setInstrumentSSGWaveform(int instNum, int wfNum) { instMan_->setInstrumentSSGWaveform(instNum, wfNum); opnaCtrl_->updateInstrumentSSG(instNum); } void BambooTracker::setInstrumentSSGWaveformEnabled(int instNum, bool enabled) { instMan_->setInstrumentSSGWaveformEnabled(instNum, enabled); opnaCtrl_->updateInstrumentSSG(instNum); } std::multiset BambooTracker::getWaveformSSGUsers(int wfNum) const { return instMan_->getWaveformSSGUsers(wfNum); } void BambooTracker::addToneNoiseSSGSequenceData(int tnNum, int data) { instMan_->addToneNoiseSSGSequenceData(tnNum, data); } void BambooTracker::removeToneNoiseSSGSequenceData(int tnNum) { instMan_->removeToneNoiseSSGSequenceData(tnNum); } void BambooTracker::setToneNoiseSSGSequenceData(int tnNum, int cnt, int data) { instMan_->setToneNoiseSSGSequenceData(tnNum, cnt, data); } void BambooTracker::addToneNoiseSSGLoop(int tnNum, const InstrumentSequenceLoop& loop) { instMan_->addToneNoiseSSGLoop(tnNum, loop); } void BambooTracker::removeToneNoiseSSGLoop(int tnNum, int begin, int end) { instMan_->removeToneNoiseSSGLoop(tnNum, begin, end); } void BambooTracker::changeToneNoiseSSGLoop(int tnNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { instMan_->changeToneNoiseSSGLoop(tnNum, prevBegin, prevEnd, loop); } void BambooTracker::clearToneNoiseSSGLoops(int tnNum) { instMan_->clearToneNoiseSSGLoops(tnNum); } void BambooTracker::setToneNoiseSSGRelease(int tnNum, const InstrumentSequenceRelease& release) { instMan_->setToneNoiseSSGRelease(tnNum, release); } void BambooTracker::setInstrumentSSGToneNoise(int instNum, int tnNum) { instMan_->setInstrumentSSGToneNoise(instNum, tnNum); opnaCtrl_->updateInstrumentSSG(instNum); } void BambooTracker::setInstrumentSSGToneNoiseEnabled(int instNum, bool enabled) { instMan_->setInstrumentSSGToneNoiseEnabled(instNum, enabled); opnaCtrl_->updateInstrumentSSG(instNum); } std::multiset BambooTracker::getToneNoiseSSGUsers(int tnNum) const { return instMan_->getToneNoiseSSGUsers(tnNum); } void BambooTracker::addEnvelopeSSGSequenceData(int envNum, const SSGEnvelopeUnit& data) { instMan_->addEnvelopeSSGSequenceData(envNum, data); } void BambooTracker::removeEnvelopeSSGSequenceData(int envNum) { instMan_->removeEnvelopeSSGSequenceData(envNum); } void BambooTracker::setEnvelopeSSGSequenceData(int envNum, int cnt, const SSGEnvelopeUnit& data) { instMan_->setEnvelopeSSGSequenceData(envNum, cnt, data); } void BambooTracker::addEnvelopeSSGLoop(int envNum, const InstrumentSequenceLoop& loop) { instMan_->addEnvelopeSSGLoop(envNum, loop); } void BambooTracker::removeEnvelopeSSGLoop(int envNum, int begin, int end) { instMan_->removeEnvelopeSSGLoop(envNum, begin, end); } void BambooTracker::changeEnvelopeSSGLoop(int envNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { instMan_->changeEnvelopeSSGLoop(envNum, prevBegin, prevEnd, loop); } void BambooTracker::clearEnvelopeSSGLoops(int envNum) { instMan_->clearEnvelopeSSGLoops(envNum); } void BambooTracker::setEnvelopeSSGRelease(int envNum, const InstrumentSequenceRelease& release) { instMan_->setEnvelopeSSGRelease(envNum, release); } void BambooTracker::setInstrumentSSGEnvelope(int instNum, int envNum) { instMan_->setInstrumentSSGEnvelope(instNum, envNum); opnaCtrl_->updateInstrumentSSG(instNum); } void BambooTracker::setInstrumentSSGEnvelopeEnabled(int instNum, bool enabled) { instMan_->setInstrumentSSGEnvelopeEnabled(instNum, enabled); opnaCtrl_->updateInstrumentSSG(instNum); } std::multiset BambooTracker::getEnvelopeSSGUsers(int envNum) const { return instMan_->getEnvelopeSSGUsers(envNum); } void BambooTracker::setArpeggioSSGType(int arpNum, SequenceType type) { instMan_->setArpeggioSSGType(arpNum, type); } void BambooTracker::addArpeggioSSGSequenceData(int arpNum, int data) { instMan_->addArpeggioSSGSequenceData(arpNum, data); } void BambooTracker::removeArpeggioSSGSequenceData(int arpNum) { instMan_->removeArpeggioSSGSequenceData(arpNum); } void BambooTracker::setArpeggioSSGSequenceData(int arpNum, int cnt, int data) { instMan_->setArpeggioSSGSequenceData(arpNum, cnt, data); } void BambooTracker::addArpeggioSSGLoop(int arpNum, const InstrumentSequenceLoop& loop) { instMan_->addArpeggioSSGLoop(arpNum, loop); } void BambooTracker::removeArpeggioSSGLoop(int arpNum, int begin, int end) { instMan_->removeArpeggioSSGLoop(arpNum, begin, end); } void BambooTracker::changeArpeggioSSGLoop(int arpNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { instMan_->changeArpeggioSSGLoop(arpNum, prevBegin, prevEnd, loop); } void BambooTracker::clearArpeggioSSGLoops(int arpNum) { instMan_->clearArpeggioSSGLoops(arpNum); } void BambooTracker::setArpeggioSSGRelease(int arpNum, const InstrumentSequenceRelease& release) { instMan_->setArpeggioSSGRelease(arpNum, release); } void BambooTracker::setInstrumentSSGArpeggio(int instNum, int arpNum) { instMan_->setInstrumentSSGArpeggio(instNum, arpNum); opnaCtrl_->updateInstrumentSSG(instNum); } void BambooTracker::setInstrumentSSGArpeggioEnabled(int instNum, bool enabled) { instMan_->setInstrumentSSGArpeggioEnabled(instNum, enabled); opnaCtrl_->updateInstrumentSSG(instNum); } std::multiset BambooTracker::getArpeggioSSGUsers(int arpNum) const { return instMan_->getArpeggioSSGUsers(arpNum); } void BambooTracker::setPitchSSGType(int ptNum, SequenceType type) { instMan_->setPitchSSGType(ptNum, type); } void BambooTracker::addPitchSSGSequenceData(int ptNum, int data) { instMan_->addPitchSSGSequenceData(ptNum, data); } void BambooTracker::removePitchSSGSequenceData(int ptNum) { instMan_->removePitchSSGSequenceData(ptNum); } void BambooTracker::setPitchSSGSequenceData(int ptNum, int cnt, int data) { instMan_->setPitchSSGSequenceData(ptNum, cnt, data); } void BambooTracker::addPitchSSGLoop(int ptNum, const InstrumentSequenceLoop& loop) { instMan_->addPitchSSGLoop(ptNum, loop); } void BambooTracker::removePitchSSGLoop(int ptNum, int begin, int end) { instMan_->removePitchSSGLoop(ptNum, begin, end); } void BambooTracker::changePitchSSGLoop(int ptNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { instMan_->changePitchSSGLoop(ptNum, prevBegin, prevEnd, loop); } void BambooTracker::clearPitchSSGLoops(int ptNum) { instMan_->clearPitchSSGLoops(ptNum); } void BambooTracker::setPitchSSGRelease(int ptNum, const InstrumentSequenceRelease& release) { instMan_->setPitchSSGRelease(ptNum, release); } void BambooTracker::setInstrumentSSGPitch(int instNum, int ptNum) { instMan_->setInstrumentSSGPitch(instNum, ptNum); opnaCtrl_->updateInstrumentSSG(instNum); } void BambooTracker::setInstrumentSSGPitchEnabled(int instNum, bool enabled) { instMan_->setInstrumentSSGPitchEnabled(instNum, enabled); opnaCtrl_->updateInstrumentSSG(instNum); } std::multiset BambooTracker::getPitchSSGUsers(int ptNum) const { return instMan_->getPitchSSGUsers(ptNum); } //--- ADPCM size_t BambooTracker::getADPCMLimit() const { return opnaCtrl_->getDRAMSize(); } size_t BambooTracker::getADPCMStoredSize() const { return opnaCtrl_->getADPCMStoredSize(); } void BambooTracker::setSampleADPCMRootKeyNumber(int sampNum, int n) { instMan_->setSampleADPCMRootKeyNumber(sampNum, n); // opnaCtrl is changed through refInstADPCM (shared_ptr) } int BambooTracker::getSampleADPCMRootKeyNumber(int sampNum) const { return instMan_->getSampleADPCMRootKeyNumber(sampNum); } void BambooTracker::setSampleADPCMRootDeltaN(int sampNum, int dn) { instMan_->setSampleADPCMRootDeltaN(sampNum, dn); // opnaCtrl is changed through refInstADPCM (shared_ptr) } int BambooTracker::getSampleADPCMRootDeltaN(int sampNum) const { return instMan_->getSampleADPCMRootDeltaN(sampNum); } void BambooTracker::setSampleADPCMRepeatEnabled(int sampNum, bool enabled) { instMan_->setSampleADPCMRepeatEnabled(sampNum, enabled); // opnaCtrl is changed through refInstADPCM (shared_ptr) } bool BambooTracker::getSampleADPCMRepeatEnabled(int sampNum) const { return instMan_->isSampleADPCMRepeatable(sampNum); } bool BambooTracker::setSampleADPCMRepeatRange(int sampNum, const SampleRepeatRange& range) { return instMan_->setSampleADPCMRepeatrange(sampNum, range); } SampleRepeatRange BambooTracker::getSampleADPCMRepeatRange(int sampNum) const { return instMan_->getSampleADPCMRepeatRange(sampNum); } void BambooTracker::storeSampleADPCMRawSample(int sampNum, const std::vector& sample) { instMan_->storeSampleADPCMRawSample(sampNum, sample); } void BambooTracker::storeSampleADPCMRawSample(int sampNum, std::vector&& sample) { instMan_->storeSampleADPCMRawSample(sampNum, std::move(sample)); } std::vector BambooTracker::getSampleADPCMRawSample(int sampNum) const { return instMan_->getSampleADPCMRawSample(sampNum); } void BambooTracker::clearSampleADPCMRawSample(int sampNum) { instMan_->clearSampleADPCMRawSample(sampNum); } bool BambooTracker::assignSampleADPCMRawSamples() { opnaCtrl_->clearSamplesADPCM(); std::vector idcs = storeOnlyUsedSamples_ ? instMan_->getSampleADPCMValidIndices() : instMan_->getSampleADPCMEntriedIndices(); bool storedAll = true; for (auto sampNum : idcs) { size_t startAddr, stopAddr; if (opnaCtrl_->storeSampleADPCM(instMan_->getSampleADPCMRawSample(sampNum), startAddr, stopAddr)) { instMan_->setSampleADPCMStartAddress(sampNum, startAddr); instMan_->setSampleADPCMStopAddress(sampNum, stopAddr); } else { storedAll = false; } } return storedAll; } size_t BambooTracker::getSampleADPCMStartAddress(int sampNum) const { return instMan_->getSampleADPCMStartAddress(sampNum); } size_t BambooTracker::getSampleADPCMStopAddress(int sampNum) const { return instMan_->getSampleADPCMStopAddress(sampNum); } void BambooTracker::setInstrumentADPCMSample(int instNum, int sampNum) { instMan_->setInstrumentADPCMSample(instNum, sampNum); opnaCtrl_->updateInstrumentADPCM(instNum); } std::multiset BambooTracker::getSampleADPCMUsers(int sampNum) const { return instMan_->getSampleADPCMUsers(sampNum); } void BambooTracker::addEnvelopeADPCMSequenceData(int envNum, int data) { instMan_->addEnvelopeADPCMSequenceData(envNum, data); } void BambooTracker::removeEnvelopeADPCMSequenceData(int envNum) { instMan_->removeEnvelopeADPCMSequenceData(envNum); } void BambooTracker::setEnvelopeADPCMSequenceData(int envNum, int cnt, int data) { instMan_->setEnvelopeADPCMSequenceData(envNum, cnt, data); } void BambooTracker::addEnvelopeADPCMLoop(int arpNum, const InstrumentSequenceLoop& loop) { instMan_->addEnvelopeADPCMLoop(arpNum, loop); } void BambooTracker::removeEnvelopeADPCMLoop(int envNum, int begin, int end) { instMan_->removeEnvelopeADPCMLoop(envNum, begin, end); } void BambooTracker::changeEnvelopeADPCMLoop(int envNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { instMan_->changeEnvelopeADPCMLoop(envNum, prevBegin, prevEnd, loop); } void BambooTracker::clearEnvelopeADPCMLoops(int envNum) { instMan_->clearEnvelopeADPCMLoops(envNum); } void BambooTracker::setEnvelopeADPCMRelease(int arpNum, const InstrumentSequenceRelease& release) { instMan_->setEnvelopeADPCMRelease(arpNum, release); } void BambooTracker::setInstrumentADPCMEnvelope(int instNum, int envNum) { instMan_->setInstrumentADPCMEnvelope(instNum, envNum); opnaCtrl_->updateInstrumentADPCM(instNum); } void BambooTracker::setInstrumentADPCMEnvelopeEnabled(int instNum, bool enabled) { instMan_->setInstrumentADPCMEnvelopeEnabled(instNum, enabled); opnaCtrl_->updateInstrumentADPCM(instNum); } std::multiset BambooTracker::getEnvelopeADPCMUsers(int envNum) const { return instMan_->getEnvelopeADPCMUsers(envNum); } void BambooTracker::setArpeggioADPCMType(int arpNum, SequenceType type) { instMan_->setArpeggioADPCMType(arpNum, type); } void BambooTracker::addArpeggioADPCMSequenceData(int arpNum, int data) { instMan_->addArpeggioADPCMSequenceData(arpNum, data); } void BambooTracker::removeArpeggioADPCMSequenceData(int arpNum) { instMan_->removeArpeggioADPCMSequenceData(arpNum); } void BambooTracker::setArpeggioADPCMSequenceData(int arpNum, int cnt, int data) { instMan_->setArpeggioADPCMSequenceData(arpNum, cnt, data); } void BambooTracker::addArpeggioADPCMLoop(int arpNum, const InstrumentSequenceLoop& loop) { instMan_->addArpeggioADPCMLoop(arpNum, loop); } void BambooTracker::removeArpeggioADPCMLoop(int arpNum, int begin, int end) { instMan_->removeArpeggioADPCMLoop(arpNum, begin, end); } void BambooTracker::changeArpeggioADPCMLoop(int arpNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { instMan_->changeArpeggioADPCMLoop(arpNum, prevBegin, prevEnd, loop); } void BambooTracker::clearArpeggioADPCMLoops(int arpNum) { instMan_->clearArpeggioADPCMLoops(arpNum); } void BambooTracker::setArpeggioADPCMRelease(int arpNum, const InstrumentSequenceRelease& release) { instMan_->setArpeggioADPCMRelease(arpNum, release); } void BambooTracker::setInstrumentADPCMArpeggio(int instNum, int arpNum) { instMan_->setInstrumentADPCMArpeggio(instNum, arpNum); opnaCtrl_->updateInstrumentADPCM(instNum); } void BambooTracker::setInstrumentADPCMArpeggioEnabled(int instNum, bool enabled) { instMan_->setInstrumentADPCMArpeggioEnabled(instNum, enabled); opnaCtrl_->updateInstrumentADPCM(instNum); } std::multiset BambooTracker::getArpeggioADPCMUsers(int arpNum) const { return instMan_->getArpeggioADPCMUsers(arpNum); } void BambooTracker::setPitchADPCMType(int ptNum, SequenceType type) { instMan_->setPitchADPCMType(ptNum, type); } void BambooTracker::addPitchADPCMSequenceData(int ptNum, int data) { instMan_->addPitchADPCMSequenceData(ptNum, data); } void BambooTracker::removePitchADPCMSequenceData(int ptNum) { instMan_->removePitchADPCMSequenceData(ptNum); } void BambooTracker::setPitchADPCMSequenceData(int ptNum, int cnt, int data) { instMan_->setPitchADPCMSequenceData(ptNum, cnt, data); } void BambooTracker::addPitchADPCMLoop(int ptNum, const InstrumentSequenceLoop& loop) { instMan_->addPitchADPCMLoop(ptNum, loop); } void BambooTracker::removePitchADPCMLoop(int ptNum, int begin, int end) { instMan_->removePitchADPCMLoop(ptNum, begin, end); } void BambooTracker::changePitchADPCMLoop(int ptNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { instMan_->changePitchADPCMLoop(ptNum, prevBegin, prevEnd, loop); } void BambooTracker::clearPitchADPCMLoops(int ptNum) { instMan_->clearPitchADPCMLoops(ptNum); } void BambooTracker::setPitchADPCMRelease(int ptNum, const InstrumentSequenceRelease& release) { instMan_->setPitchADPCMRelease(ptNum, release); } void BambooTracker::setInstrumentADPCMPitch(int instNum, int ptNum) { instMan_->setInstrumentADPCMPitch(instNum, ptNum); opnaCtrl_->updateInstrumentADPCM(instNum); } void BambooTracker::setInstrumentADPCMPitchEnabled(int instNum, bool enabled) { instMan_->setInstrumentADPCMPitchEnabled(instNum, enabled); opnaCtrl_->updateInstrumentADPCM(instNum); } std::multiset BambooTracker::getPitchADPCMUsers(int ptNum) const { return instMan_->getPitchADPCMUsers(ptNum); } void BambooTracker::addPanADPCMSequenceData(int panNum, int data) { instMan_->addPanADPCMSequenceData(panNum, data); } void BambooTracker::removePanADPCMSequenceData(int panNum) { instMan_->removePanADPCMSequenceData(panNum); } void BambooTracker::setPanADPCMSequenceData(int panNum, int cnt, int data) { instMan_->setPanADPCMSequenceData(panNum, cnt, data); } void BambooTracker::addPanADPCMLoop(int panNum, const InstrumentSequenceLoop& loop) { instMan_->addPanADPCMLoop(panNum, loop); } void BambooTracker::removePanADPCMLoop(int panNum, int begin, int end) { instMan_->removePanADPCMLoop(panNum, begin, end); } void BambooTracker::changePanADPCMLoop(int panNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { instMan_->changePanADPCMLoop(panNum, prevBegin, prevEnd, loop); } void BambooTracker::clearPanADPCMLoops(int panNum) { instMan_->clearPanADPCMLoops(panNum); } void BambooTracker::setPanADPCMRelease(int panNum, const InstrumentSequenceRelease& release) { instMan_->setPanADPCMRelease(panNum, release); } void BambooTracker::setInstrumentADPCMPan(int instNum, int panNum) { instMan_->setInstrumentADPCMPan(instNum, panNum); opnaCtrl_->updateInstrumentADPCM(instNum); } void BambooTracker::setInstrumentADPCMPanEnabled(int instNum, bool enabled) { instMan_->setInstrumentADPCMPanEnabled(instNum, enabled); opnaCtrl_->updateInstrumentADPCM(instNum); } std::multiset BambooTracker::getPanADPCMUsers(int panNum) const { return instMan_->getPanADPCMUsers(panNum); } //--- Drumkit void BambooTracker::setInstrumentDrumkitSample(int instNum, int key, int sampNum) { instMan_->setInstrumentDrumkitSamples(instNum, key, sampNum); opnaCtrl_->updateInstrumentDrumkit(instNum, key); } void BambooTracker::setInstrumentDrumkitSampleEnabled(int instNum, int key, bool enabled) { instMan_->setInstrumentDrumkitSamplesEnabled(instNum, key, enabled); opnaCtrl_->updateInstrumentADPCM(instNum); } void BambooTracker::setInstrumentDrumkitPitch(int instNum, int key, int pitch) { instMan_->setInstrumentDrumkitPitch(instNum, key, pitch); opnaCtrl_->updateInstrumentDrumkit(instNum, key); } void BambooTracker::setInstrumentDrumkitPan(int instNum, int key, int pan) { instMan_->setInstrumentDrumkitPan(instNum, key, pan); opnaCtrl_->updateInstrumentDrumkit(instNum, key); } /********** Song edit **********/ int BambooTracker::getCurrentSongNumber() const { return curSongNum_; } void BambooTracker::setCurrentSongNumber(int num) { curSongNum_ = num; curTrackNum_ = 0; curOrderNum_ = 0; curStepNum_ = 0; mkOrder_ = -1; mkStep_ = -1; auto& song = mod_->getSong(curSongNum_); songStyle_ = song.getStyle(); playback_->setSong(mod_, curSongNum_); /*jamMan_->clear();*/ // Reset opnaCtrl_->reset(); opnaCtrl_->setMode(songStyle_.type); tickCounter_->resetCount(); tickCounter_->setTempo(song.getTempo()); tickCounter_->setSpeed(song.getSpeed()); tickCounter_->setGroove(mod_->getGroove(song.getGroove())); tickCounter_->setGrooveState(song.isUsedTempo() ? GrooveState::Invalid : GrooveState::ValidByGlobal); std::unordered_map pairs = { { SoundSource::FM, Song::getFMChannelCount(songStyle_.type) }, { SoundSource::SSG, 3 }, { SoundSource::RHYTHM, 6 }, { SoundSource::ADPCM, 1 }, }; for (auto& pair : pairs) { muteState_[pair.first] = std::vector(pair.second, false); for (int ch = 0; ch < pair.second; ++ch) opnaCtrl_->setMuteState(pair.first, ch, false); } } /********** Order edit **********/ int BambooTracker::getCurrentOrderNumber() const { return curOrderNum_; } void BambooTracker::setCurrentOrderNumber(int num) { curOrderNum_ = num; } /********** Pattern edit **********/ int BambooTracker::getCurrentStepNumber() const { return curStepNum_; } void BambooTracker::setCurrentStepNumber(int num) { curStepNum_ = num; } /********** Undo-Redo **********/ bool BambooTracker::undo() { return comMan_.undo(); } bool BambooTracker::redo() { return comMan_.redo(); } void BambooTracker::clearCommandHistory() { comMan_.clear(); } /********** Jam mode **********/ void BambooTracker::toggleJamMode() { if (jamMan_->toggleJamMode() && !isPlaySong()) { jamMan_->polyphonic(true); } else { jamMan_->polyphonic(false); } } bool BambooTracker::isJamMode() const { return jamMan_->isJamMode(); } void BambooTracker::jamKeyOn(JamKey key, bool volumeSet) { int keyNum = jam_utils::makeNote(curOctave_, key).getNoteNumber(); const TrackAttribute& attrib = songStyle_.trackAttribs[static_cast(curTrackNum_)]; funcJamKeyOn(key, keyNum, attrib, volumeSet); } void BambooTracker::jamKeyOn(int keyNum, bool volumeSet) { const TrackAttribute& attrib = songStyle_.trackAttribs[static_cast(curTrackNum_)]; funcJamKeyOn(JamKey::MidiKey, keyNum, attrib, volumeSet); } void BambooTracker::jamKeyOnForced(JamKey key, SoundSource src, bool volumeSet, std::shared_ptr inst) { int keyNum = jam_utils::makeNote(curOctave_, key).getNoteNumber(); const TrackAttribute& attrib = songStyle_.trackAttribs[static_cast(curTrackNum_)]; if (attrib.source == src) { funcJamKeyOn(key, keyNum, attrib, volumeSet, inst); } else { auto it = utils::findIf(songStyle_.trackAttribs, [src](const TrackAttribute& attrib) { return attrib.source == src; }); funcJamKeyOn(key, keyNum, *it, volumeSet, inst); } } void BambooTracker::jamKeyOnForced(int keyNum, SoundSource src, bool volumeSet, std::shared_ptr inst) { const TrackAttribute& attrib = songStyle_.trackAttribs[static_cast(curTrackNum_)]; if (attrib.source == src) { funcJamKeyOn(JamKey::MidiKey, keyNum, attrib, volumeSet, inst); } else { auto it = utils::findIf(songStyle_.trackAttribs, [src](const TrackAttribute& attrib) { return attrib.source == src; }); funcJamKeyOn(JamKey::MidiKey, keyNum, *it, volumeSet, inst); } } void BambooTracker::funcJamKeyOn(JamKey key, int keyNum, const TrackAttribute& attrib, bool volumeSet, std::shared_ptr inst) { if (playback_->isPlayingStep()) playback_->stopPlaySong(); // Reset if (attrib.source == SoundSource::RHYTHM) { if (volumeSet) opnaCtrl_->setVolumeRhythm(attrib.channelInSource, std::min(curVolume_, bt_defs::NSTEP_RHYTHM_VOLUME - 1)); opnaCtrl_->setKeyOnFlagRhythm(attrib.channelInSource); opnaCtrl_->updateKeyOnOffStatusRhythm(true); } else { std::vector&& list = jamMan_->keyOn(key, attrib.channelInSource, attrib.source, keyNum); if (list.size() == 2) { // Key off JamKeyInfo& offInfo = list[1]; switch (offInfo.source) { case SoundSource::FM: if (songStyle_.type == SongType::FM3chExpanded && offInfo.channelInSource == 2) { opnaCtrl_->keyOffFM(2, true); opnaCtrl_->keyOffFM(6, true); opnaCtrl_->keyOffFM(7, true); opnaCtrl_->keyOffFM(8, true); } else { opnaCtrl_->keyOffFM(offInfo.channelInSource, true); } break; case SoundSource::SSG: opnaCtrl_->keyOffSSG(offInfo.channelInSource, true); break; case SoundSource::ADPCM: opnaCtrl_->keyOffADPCM(true); break; default: break; } } if (!inst) { // Use current instrument if not specified inst = instMan_->getInstrumentSharedPtr(curInstNum_); } JamKeyInfo& onInfo = list.front(); Note&& note = jam_utils::makeNote(onInfo, curOctave_); switch (onInfo.source) { case SoundSource::FM: if (auto fm = std::dynamic_pointer_cast(inst)) opnaCtrl_->setInstrumentFM(onInfo.channelInSource, fm); if (volumeSet) { int vol; if (volFMReversed_) vol = effect_utils::reverseFmVolume(curVolume_, true); else vol = std::min(curVolume_, bt_defs::NSTEP_FM_VOLUME - 1); opnaCtrl_->setVolumeFM(onInfo.channelInSource, vol); } if (songStyle_.type == SongType::FM3chExpanded && onInfo.channelInSource == 2) { opnaCtrl_->keyOnFM(2, note, true); opnaCtrl_->keyOnFM(6, note, true); opnaCtrl_->keyOnFM(7, note, true); opnaCtrl_->keyOnFM(8, note, true); } else { opnaCtrl_->keyOnFM(onInfo.channelInSource, note, true); } break; case SoundSource::SSG: if (auto ssg = std::dynamic_pointer_cast(inst)) opnaCtrl_->setInstrumentSSG(onInfo.channelInSource, ssg); if (volumeSet) opnaCtrl_->setVolumeSSG(onInfo.channelInSource, std::min(curVolume_, 0xf)); opnaCtrl_->keyOnSSG(onInfo.channelInSource, note, true); break; case SoundSource::ADPCM: if (auto adpcm = std::dynamic_pointer_cast(inst)) opnaCtrl_->setInstrumentADPCM(adpcm); else if (auto kit = std::dynamic_pointer_cast(inst)) opnaCtrl_->setInstrumentDrumkit(kit); if (volumeSet) opnaCtrl_->setVolumeADPCM(curVolume_); opnaCtrl_->keyOnADPCM(note, true); break; default: break; } } } void BambooTracker::jamKeyOff(JamKey key) { int keyNum = jam_utils::makeNote(curOctave_, key).getNoteNumber(); const TrackAttribute& attrib = songStyle_.trackAttribs[static_cast(curTrackNum_)]; funcJamKeyOff(key, keyNum, attrib); } void BambooTracker::jamKeyOff(int keyNum) { const TrackAttribute& attrib = songStyle_.trackAttribs[static_cast(curTrackNum_)]; funcJamKeyOff(JamKey::MidiKey, keyNum, attrib); } void BambooTracker::jamKeyOffForced(JamKey key, SoundSource src) { int keyNum = jam_utils::makeNote(curOctave_, key).getNoteNumber(); const TrackAttribute& attrib = songStyle_.trackAttribs[static_cast(curTrackNum_)]; if (attrib.source == src) { funcJamKeyOff(key, keyNum, attrib); } else { auto it = utils::findIf(songStyle_.trackAttribs, [src](const TrackAttribute& attrib) { return attrib.source == src; }); funcJamKeyOff(key, keyNum, *it); } } void BambooTracker::jamKeyOffForced(int keyNum, SoundSource src) { const TrackAttribute& attrib = songStyle_.trackAttribs[static_cast(curTrackNum_)]; if (attrib.source == src) { funcJamKeyOff(JamKey::MidiKey, keyNum, attrib); } else { auto it = utils::findIf(songStyle_.trackAttribs, [src](const TrackAttribute& attrib) { return attrib.source == src; }); funcJamKeyOff(JamKey::MidiKey, keyNum, *it); } } void BambooTracker::funcJamKeyOff(JamKey key, int keyNum, const TrackAttribute& attrib) { if (attrib.source == SoundSource::RHYTHM) { opnaCtrl_->setKeyOffFlagRhythm(attrib.channelInSource); opnaCtrl_->updateKeyOnOffStatusRhythm(true); } else { JamKeyInfo&& info = jamMan_->keyOff(key, keyNum); if (info.channelInSource > -1) { // Key still sound switch (info.source) { case SoundSource::FM: if (songStyle_.type == SongType::FM3chExpanded && info.channelInSource == 2) { opnaCtrl_->keyOffFM(2, true); opnaCtrl_->keyOffFM(6, true); opnaCtrl_->keyOffFM(7, true); opnaCtrl_->keyOffFM(8, true); } else { opnaCtrl_->keyOffFM(info.channelInSource, true); } break; case SoundSource::SSG: opnaCtrl_->keyOffSSG(info.channelInSource, true); break; case SoundSource::ADPCM: opnaCtrl_->keyOffADPCM(true); break; default: break; } } } } void BambooTracker::jamkeyOffAll() { std::vector&& onList = jamMan_->reset(); for (auto& info : onList) { if (info.channelInSource > -1) { // Key still sound switch (info.source) { case SoundSource::FM: if (songStyle_.type == SongType::FM3chExpanded && info.channelInSource == 2) { opnaCtrl_->keyOffFM(2, true); opnaCtrl_->keyOffFM(6, true); opnaCtrl_->keyOffFM(7, true); opnaCtrl_->keyOffFM(8, true); } else { opnaCtrl_->keyOffFM(info.channelInSource, true); } break; case SoundSource::SSG: opnaCtrl_->keyOffSSG(info.channelInSource, true); break; case SoundSource::ADPCM: opnaCtrl_->keyOffADPCM(true); break; default: break; } } } for (int i = 0; i < 6; ++i) opnaCtrl_->setKeyOffFlagRhythm(i); opnaCtrl_->updateRegisterStates(); } bool BambooTracker::assignADPCMBeforeForcedJamKeyOn( std::shared_ptr inst, std::unordered_map>& sampAddrs) { size_t start, stop; bool isAssignedAll = false; switch (inst->getType()) { case InstrumentType::ADPCM: { opnaCtrl_->clearSamplesADPCM(); if (opnaCtrl_->storeSampleADPCM( std::dynamic_pointer_cast(inst)->getRawSample(), start, stop)) { sampAddrs[0] = {{ start, stop }}; isAssignedAll = true; } break; } case InstrumentType::Drumkit: { opnaCtrl_->clearSamplesADPCM(); std::vector> addrs; auto kit = std::dynamic_pointer_cast(inst); for (const int& key : kit->getAssignedKeys()) { int n = kit->getSampleNumber(key); if (!sampAddrs.count(n)) { bool assigned = opnaCtrl_->storeSampleADPCM(kit->getRawSample(key), start, stop); if (assigned) sampAddrs[n] = {{ start, stop }}; isAssignedAll &= assigned; } } break; } default: break; } return isAssignedAll; } /********** Play song **********/ void BambooTracker::startPlaySong() { playback_->startPlaySong(curOrderNum_); startPlay(); if (isFollowPlay_) curStepNum_ = 0; } void BambooTracker::startPlayFromStart() { playback_->startPlayFromStart(); startPlay(); if (isFollowPlay_) { curOrderNum_ = 0; curStepNum_ = 0; } } void BambooTracker::startPlayPattern() { playback_->startPlayPattern(curOrderNum_); startPlay(); if (isFollowPlay_) curStepNum_ = 0; } void BambooTracker::startPlayFromCurrentStep() { playback_->startPlayFromPosition(curOrderNum_, curStepNum_); startPlay(); } bool BambooTracker::startPlayFromMarker() { Song& song = mod_->getSong(curSongNum_); if (mkOrder_ != -1 && mkOrder_ < static_cast(song.getOrderSize()) && mkStep_ != -1 && mkStep_ < static_cast(song.getPatternSizeFromOrderNumber(mkOrder_))) { playback_->startPlayFromPosition(mkOrder_, mkStep_); startPlay(); return true; } return false; } void BambooTracker::playStep() { playback_->playStep(curOrderNum_, curStepNum_); for (auto& pair : muteState_) { for (size_t i = 0; i < pair.second.size(); ++i) { opnaCtrl_->setMuteState(pair.first, static_cast(i), pair.second[i]); } } } void BambooTracker::startPlay() { jamMan_->polyphonic(false); for (auto& pair : muteState_) { for (size_t i = 0; i < pair.second.size(); ++i) { opnaCtrl_->setMuteState(pair.first, static_cast(i), pair.second[i]); } } } void BambooTracker::stopPlaySong() { playback_->stopPlaySong(); jamMan_->polyphonic(true); for (auto& pair : muteState_) { for (size_t i = 0; i < pair.second.size(); ++i) { opnaCtrl_->setMuteState(pair.first, static_cast(i), false); } } } bool BambooTracker::isPlaySong() const { return playback_->isPlaySong(); } void BambooTracker::setTrackMuteState(int trackNum, bool isMute) { auto& ta = songStyle_.trackAttribs[static_cast(trackNum)]; muteState_.at(ta.source).at(static_cast(ta.channelInSource)) = isMute; if (isPlaySong()) opnaCtrl_->setMuteState(ta.source, ta.channelInSource, isMute); } bool BambooTracker::isMute(int trackNum) { auto& ta = songStyle_.trackAttribs[static_cast(trackNum)]; return muteState_.at(ta.source).at(ta.channelInSource); } void BambooTracker::setFollowPlay(bool isFollowed) { isFollowPlay_ = isFollowed; if (isFollowed) { int odr = playback_->getPlayingOrderNumber(); if (odr >= 0) { curOrderNum_ = odr; curStepNum_ = playback_->getPlayingStepNumber(); } } } bool BambooTracker::isFollowPlay() const { return isFollowPlay_; } int BambooTracker::getPlayingOrderNumber() const { return playback_->getPlayingOrderNumber(); } int BambooTracker::getPlayingStepNumber() const { return playback_->getPlayingStepNumber(); } void BambooTracker::setMarker(int order, int step) { if (mkOrder_ == order && mkStep_ == step) { mkOrder_ = -1; mkStep_ = -1; } else { mkOrder_ = order; mkStep_ = step; } } int BambooTracker::getMarkerOrder() const { return mkOrder_; } int BambooTracker::getMarkerStep() const { return mkStep_; } /********** Export **********/ namespace { void checkNextPositionOfLastStepAndStepSize(Song& song, int& endOrder, int& endStep) { endOrder = 0; endStep = 0; std::vector attribs = song.getTrackAttributes(); std::unordered_set orderStepMap; int lastOrder = static_cast(song.getOrderSize()) - 1; for (int curOrder = 0; !orderStepMap.count(curOrder); curOrder = endOrder) { orderStepMap.insert(curOrder); // Arrived flag // Default next order endOrder = (endOrder + 1) % (lastOrder + 1); endStep = 0; // Check jump effect for (auto attrib : attribs) { Step& step = song.getTrack(attrib.number).getPatternFromOrderNumber(curOrder) .getStep(static_cast(song.getPatternSizeFromOrderNumber(curOrder)) - 1); for (int i = 0; i < Step::N_EFFECT; ++i) { Effect&& eff = effect_utils::validateEffect(attrib.source, step.getEffect(i)); switch (eff.type) { case EffectType::PositionJump: if (eff.value <= lastOrder) { endOrder = eff.value; endStep = 0; } break; case EffectType::SongEnd: endOrder = -1; endStep = -1; return; case EffectType::PatternBreak: if (curOrder == lastOrder && eff.value < static_cast(song.getPatternSizeFromOrderNumber(0))) { endOrder = 0; endStep = eff.value; } else if (eff.value < static_cast(song.getPatternSizeFromOrderNumber(curOrder + 1))) { endOrder = curOrder + 1; endStep = eff.value; } break; default: break; } } } } } } bool BambooTracker::exportToWav(io::WavContainer& container, int loopCnt, ExportCancellCallback checkFunc) { int tmpRate = opnaCtrl_->getRate(); opnaCtrl_->setRate(static_cast(container.getSampleRate())); size_t sampCnt = static_cast(opnaCtrl_->getRate() * opnaCtrl_->getDuration() / 1000); size_t intrCnt = static_cast(opnaCtrl_->getRate()) / mod_->getTickFrequency(); size_t intrCntRest = 0; std::vector buf(sampCnt << 1); int endOrder, endStep; checkNextPositionOfLastStepAndStepSize(mod_->getSong(curSongNum_), endOrder, endStep); bool endFlag = false; bool tmpFollow = std::exchange(isFollowPlay_, false); startPlayFromStart(); while (true) { size_t sampCntRest = sampCnt; while (sampCntRest) { if (!intrCntRest) { // Interruption intrCntRest = intrCnt; // Set counts to next interruption if (!streamCountUp()) { if (checkFunc()) { // Update lambda function stopPlaySong(); isFollowPlay_ = tmpFollow; opnaCtrl_->setRate(tmpRate); return false; } int playOrder = playback_->getPlayingOrderNumber(); int playStep = playback_->getPlayingStepNumber(); if ((playOrder == -1 && playStep == -1) || (playOrder == endOrder && playStep == endStep && !(loopCnt--))){ endFlag = true; break; } } } size_t count = std::min(intrCntRest, sampCntRest); sampCntRest -= count; intrCntRest -= count; bool result = opnaCtrl_->getStreamSamples(buf.data(), count); if (!result) { stopPlaySong(); isFollowPlay_ = tmpFollow; opnaCtrl_->setRate(tmpRate); return false; } container.appendSample(buf.data(), count); } if (endFlag) break; } stopPlaySong(); isFollowPlay_ = tmpFollow; opnaCtrl_->setRate(tmpRate); return true; } bool BambooTracker::exportToVgm(io::BinaryContainer& container, int target, bool gd3TagEnabled, const io::GD3Tag& tag, bool shouldSetMix, double gain, ExportCancellCallback checkFunc) { int tmpRate = opnaCtrl_->getRate(); opnaCtrl_->setRate(44100); double dblIntrCnt = 44100.0 / static_cast(mod_->getTickFrequency()); size_t intrCnt = static_cast(dblIntrCnt); double intrCntDiff = dblIntrCnt - intrCnt; double intrCntRest = 0; int loopOrder, loopStep; checkNextPositionOfLastStepAndStepSize(mod_->getSong(curSongNum_), loopOrder, loopStep); bool loopFlag = (loopOrder != -1); int endCnt = (loopOrder == -1) ? 0 : 1; bool tmpFollow = std::exchange(isFollowPlay_, false); uint32_t loopPoint = 0; uint32_t loopPointSamples = 0; auto exCntr = std::make_shared(target, mod_->getTickFrequency()); // Set ADPCM opnaCtrl_->clearSamplesADPCM(); std::vector rom; for (auto sampNum : instMan_->getSampleADPCMValidIndices()) { std::vector&& sample = instMan_->getSampleADPCMRawSample(sampNum); size_t startAddr, stopAddr; if (opnaCtrl_->storeSampleADPCM(sample, startAddr, stopAddr)) { instMan_->setSampleADPCMStartAddress(sampNum, startAddr); instMan_->setSampleADPCMStopAddress(sampNum, stopAddr); rom.resize((stopAddr + 1) << 5); std::copy(sample.begin(), sample.end(), rom.begin() + static_cast(startAddr << 5)); } } exCntr->setDataBlock(std::move(rom)); opnaCtrl_->setExportContainer(exCntr); startPlayFromStart(); exCntr->forceMoveLoopPoint(); while (true) { if (!streamCountUp()) { if (checkFunc()) { // Update lambda function stopPlaySong(); isFollowPlay_ = tmpFollow; opnaCtrl_->setRate(tmpRate); return false; } int playOrder = playback_->getPlayingOrderNumber(); int playStep = playback_->getPlayingStepNumber(); if (playOrder == loopOrder && playStep == loopStep && !(endCnt--)) break; if (loopFlag && loopOrder == playOrder && loopStep == playStep) { loopPoint = exCntr->setLoopPoint(); loopPointSamples = exCntr->getSampleLength(); } } intrCntRest += intrCntDiff; size_t extraIntrCnt = static_cast(intrCntRest); intrCntRest -= extraIntrCnt; exCntr->elapse(intrCnt + extraIntrCnt); } opnaCtrl_->setExportContainer(); stopPlaySong(); isFollowPlay_ = tmpFollow; opnaCtrl_->setRate(tmpRate); const io::GD3Tag* gd3Tag = gd3TagEnabled ? &tag : nullptr; std::unique_ptr mix; if (shouldSetMix) { mix = std::make_unique(opnaCtrl_->getMasterVolumeFM(), opnaCtrl_->getMasterVolumeSSG(), gain); } try { io::writeVgm(container, target, exCntr->getData(), CHIP_CLOCK, mod_->getTickFrequency(), loopFlag, loopPoint, exCntr->getSampleLength() - loopPointSamples, exCntr->getSampleLength(), gd3Tag, mix.get()); return true; } catch (...) { throw; } } bool BambooTracker::exportToS98(io::BinaryContainer& container, int target, bool tagEnabled, const io::S98Tag& tag, int rate, ExportCancellCallback checkFunc) { int tmpRate = opnaCtrl_->getRate(); opnaCtrl_->setRate(rate); double dblIntrCnt = static_cast(rate) / static_cast(mod_->getTickFrequency()); size_t intrCnt = static_cast(dblIntrCnt); double intrCntDiff = dblIntrCnt - intrCnt; double intrCntRest = 0; int loopOrder, loopStep; checkNextPositionOfLastStepAndStepSize(mod_->getSong(curSongNum_), loopOrder, loopStep); bool loopFlag = (loopOrder != -1); int endCnt = (loopOrder == -1) ? 0 : 1; bool tmpFollow = std::exchange(isFollowPlay_, false); uint32_t loopPoint = 0; auto exCntr = std::make_shared(target); opnaCtrl_->setExportContainer(exCntr); startPlayFromStart(); assignSampleADPCMRawSamples(); exCntr->forceMoveLoopPoint(); while (true) { exCntr->getData(); // Set wait counts if (!streamCountUp()) { if (checkFunc()) { // Update lambda function stopPlaySong(); isFollowPlay_ = tmpFollow; opnaCtrl_->setRate(tmpRate); return false; } int playOrder = playback_->getPlayingOrderNumber(); int playStep = playback_->getPlayingStepNumber(); if (playOrder == loopOrder && playStep == loopStep && !(endCnt--)) break; if (loopFlag && loopOrder == playOrder && loopStep == playStep) { loopPoint = exCntr->setLoopPoint(); } } intrCntRest += intrCntDiff; size_t extraIntrCnt = static_cast(intrCntRest); intrCntRest -= extraIntrCnt; exCntr->elapse(intrCnt + extraIntrCnt); } opnaCtrl_->setExportContainer(); stopPlaySong(); isFollowPlay_ = tmpFollow; opnaCtrl_->setRate(tmpRate); try { io::writeS98(container, target, exCntr->getData(), CHIP_CLOCK, static_cast(rate), loopFlag, loopPoint, tagEnabled, tag); return true; } catch (...) { throw; } } /********** Real chip interface **********/ void BambooTracker::connectToRealChip(RealChipInterfaceType type, RealChipInterfaceGeneratorFunc* f) { opnaCtrl_->connectToRealChip(type, f); } RealChipInterfaceType BambooTracker::getRealChipInterfaceType() const { return opnaCtrl_->getRealChipInterfaceType(); } bool BambooTracker::hasConnectedToRealChip() const { return opnaCtrl_->hasConnectedToRealChip(); } /********** Stream events **********/ int BambooTracker::streamCountUp() { int state = playback_->streamCountUp(); if (!state && isFollowPlay_ && !playback_->isPlayingStep()) { // Step int odr = playback_->getPlayingOrderNumber(); if (odr >= 0) { curOrderNum_ = odr; curStepNum_ = playback_->getPlayingStepNumber(); } } return state; } bool BambooTracker::getStreamSamples(int16_t *container, size_t nSamples) { return opnaCtrl_->getStreamSamples(container, nSamples); } void BambooTracker::killSound() { jamMan_->reset(); opnaCtrl_->reset(); } /********** Stream details **********/ int BambooTracker::getStreamRate() const { return opnaCtrl_->getRate(); } void BambooTracker::setStreamRate(int rate) { opnaCtrl_->setRate(rate); } int BambooTracker::getStreamDuration() const { return opnaCtrl_->getDuration(); } void BambooTracker::setStreamDuration(int duration) { opnaCtrl_->setDuration(duration); } int BambooTracker::getStreamTempo() const { return tickCounter_->getTempo(); } int BambooTracker::getStreamSpeed() const { return tickCounter_->getSpeed(); } bool BambooTracker::getStreamGrooveEnabled() const { return tickCounter_->getGrooveEnabled(); } void BambooTracker::setMasterVolume(int percentage) { opnaCtrl_->setMasterVolume(percentage); } void BambooTracker::setMasterVolumeFM(double dB) { opnaCtrl_->setMasterVolumeFM(dB); } void BambooTracker::setMasterVolumeSSG(double dB) { opnaCtrl_->setMasterVolumeSSG(dB); } /********** Module details **********/ /*----- Module -----*/ void BambooTracker::makeNewModule() { makeNewModule(true); } void BambooTracker::makeNewModule(bool withInstrument) { stopPlaySong(); clearAllInstrument(); opnaCtrl_->reset(); mod_ = std::make_shared(); tickCounter_->setInterruptRate(mod_->getTickFrequency()); setCurrentSongNumber(0); if (withInstrument) { addInstrument(0, InstrumentType::FM, u8"Instrument 00"); curInstNum_ = 0; } else { curInstNum_ = -1; } clearCommandHistory(); } void BambooTracker::loadModule(io::BinaryContainer& container) { makeNewModule(false); std::exception_ptr ep; try { io::ModuleIO::getInstance().loadModule(container, mod_, instMan_); } catch (...) { ep = std::current_exception(); } tickCounter_->setInterruptRate(mod_->getTickFrequency()); setCurrentSongNumber(0); clearCommandHistory(); if (ep) std::rethrow_exception(ep); } void BambooTracker::saveModule(io::BinaryContainer& container) { io::ModuleIO::getInstance().saveModule(container, mod_, instMan_); } void BambooTracker::setModulePath(const std::string& path) { mod_->setFilePath(path); } std::string BambooTracker::getModulePath() const { return mod_->getFilePath(); } void BambooTracker::setModuleTitle(const std::string& title) { mod_->setTitle(title); } std::string BambooTracker::getModuleTitle() const { return mod_->getTitle(); } void BambooTracker::setModuleAuthor(const std::string& author) { mod_->setAuthor(author); } std::string BambooTracker::getModuleAuthor() const { return mod_->getAuthor(); } void BambooTracker::setModuleCopyright(const std::string& copyright) { mod_->setCopyright(copyright); } std::string BambooTracker::getModuleCopyright() const { return mod_->getCopyright(); } void BambooTracker::setModuleComment(const std::string& comment) { mod_->setComment(comment); } std::string BambooTracker::getModuleComment() const { return mod_->getComment(); } void BambooTracker::setModuleTickFrequency(unsigned int freq) { mod_->setTickFrequency(freq); tickCounter_->setInterruptRate(freq); } unsigned int BambooTracker::getModuleTickFrequency() const { return mod_->getTickFrequency(); } void BambooTracker::setModuleStepHighlight1Distance(size_t dist) { mod_->setStepHighlight1Distance(dist); } size_t BambooTracker::getModuleStepHighlight1Distance() const { return mod_->getStepHighlight1Distance(); } void BambooTracker::setModuleStepHighlight2Distance(size_t dist) { mod_->setStepHighlight2Distance(dist); } size_t BambooTracker::getModuleStepHighlight2Distance() const { return mod_->getStepHighlight2Distance(); } void BambooTracker::setModuleMixerType(MixerType type) { mod_->setMixerType(type); } MixerType BambooTracker::getModuleMixerType() const { return mod_->getMixerType(); } void BambooTracker::setModuleCustomMixerFMLevel(double level) { mod_->setCustomMixerFMLevel(level); } double BambooTracker::getModuleCustomMixerFMLevel() const { return mod_->getCustomMixerFMLevel(); } void BambooTracker::setModuleCustomMixerSSGLevel(double level) { mod_->setCustomMixerSSGLevel(level); } double BambooTracker::getModuleCustomMixerSSGLevel() const { return mod_->getCustomMixerSSGLevel(); } size_t BambooTracker::getGrooveCount() const { return mod_->getGrooveCount(); } void BambooTracker::setGroove(int num, const std::vector& seq) { mod_->setGroove(num, seq); } void BambooTracker::setGrooves(const std::vector>& seqs) { mod_->setGrooves(seqs); } std::vector BambooTracker::getGroove(int num) const { return mod_->getGroove(num); } void BambooTracker::clearUnusedPatterns() { mod_->clearUnusedPatterns(); } std::unordered_map BambooTracker::replaceDuplicateInstrumentsInPatterns() { std::unordered_map map = instMan_->getDuplicateInstrumentMap(); mod_->replaceDuplicateInstrumentsInPatterns(map); return map; } void BambooTracker::clearUnusedADPCMSamples() { instMan_->clearUnusedSamplesADPCM(); } /*----- Song -----*/ void BambooTracker::setSongTitle(int songNum, const std::string& title) { mod_->getSong(songNum).setTitle(title); } std::string BambooTracker::getSongTitle(int songNum) const { return mod_->getSong(songNum).getTitle(); } void BambooTracker::setSongTempo(int songNum, int tempo) { mod_->getSong(songNum).setTempo(tempo); if (curSongNum_ == songNum) tickCounter_->setTempo(tempo); } int BambooTracker::getSongTempo(int songNum) const { return mod_->getSong(songNum).getTempo(); } void BambooTracker::setSongGroove(int songNum, int groove) { mod_->getSong(songNum).setGroove(groove); tickCounter_->setGroove(mod_->getGroove(groove)); } int BambooTracker::getSongGroove(int songNum) const { return mod_->getSong(songNum).getGroove(); } void BambooTracker::toggleTempoOrGrooveInSong(int songNum, bool isTempo) { mod_->getSong(songNum).toggleTempoOrGroove(isTempo); tickCounter_->setGrooveState(isTempo ? GrooveState::Invalid : GrooveState::ValidByGlobal); } bool BambooTracker::isUsedTempoInSong(int songNum) const { return mod_->getSong(songNum).isUsedTempo(); } SongStyle BambooTracker::getSongStyle(int songNum) const { return mod_->getSong(songNum).getStyle(); } void BambooTracker::changeSongType(int songNum, SongType type) { mod_->getSong(songNum).changeType(type); } void BambooTracker::setSongSpeed(int songNum, int speed) { mod_->getSong(songNum).setSpeed(speed); if (curSongNum_ == songNum) tickCounter_->setSpeed(speed); } int BambooTracker::getSongSpeed(int songNum) const { return mod_->getSong(songNum).getSpeed(); } size_t BambooTracker::getSongCount() const { return mod_->getSongCount(); } void BambooTracker::addSong(SongType songType, const std::string& title) { mod_->addSong(songType, title); } void BambooTracker::sortSongs(const std::vector& numbers) { mod_->sortSongs(std::move(numbers)); } void BambooTracker::transposeSong(int songNum, int semitones, const std::vector& excludeInsts) { mod_->getSong(songNum).transpose(semitones, excludeInsts); } void BambooTracker::swapTracks(int songNum, int track1, int track2) { mod_->getSong(songNum).swapTracks(track1, track2); } double BambooTracker::estimateSongLength(int songNum) const { SongLengthCalculator calc(*mod_.get(), songNum); return calc.approximateLengthBySecond(); } size_t BambooTracker::getTotalStepCount(int songNum, size_t loopCnt) const { size_t introSize, loopSize; SongLengthCalculator calc(*mod_.get(), songNum); calc.totalStepCount(introSize, loopSize); return introSize + loopSize * loopCnt; } /*----- Bookmark -----*/ void BambooTracker::addBookmark(int songNum, const std::string& name, int order, int step) { mod_->getSong(songNum).addBookmark(name, order, step); } void BambooTracker::changeBookmark(int songNum, int i, const std::string& name, int order, int step) { mod_->getSong(songNum).changeBookmark(i, name, order, step); } void BambooTracker::removeBookmark(int songNum, int i) { mod_->getSong(songNum).removeBookmark(i); } void BambooTracker::clearBookmark(int songNum) { mod_->getSong(songNum).clearBookmark(); } void BambooTracker::swapBookmarks(int songNum, int a, int b) { mod_->getSong(songNum).swapBookmarks(a, b); } void BambooTracker::sortBookmarkByPosition(int songNum) { mod_->getSong(songNum).sortBookmarkByPosition(); } void BambooTracker::sortBookmarkByName(int songNum) { mod_->getSong(songNum).sortBookmarkByName(); } Bookmark BambooTracker::getBookmark(int songNum, int i) const { return mod_->getSong(songNum).getBookmark(i); } std::vector BambooTracker::findBookmarks(int songNum, int order, int step) { return mod_->getSong(songNum).findBookmarks(order, step); } Bookmark BambooTracker::getPreviousBookmark(int songNum, int order, int step) { return mod_->getSong(songNum).getPreviousBookmark(order, step); } Bookmark BambooTracker::getNextBookmark(int songNum, int order, int step) { return mod_->getSong(songNum).getNextBookmark(order, step); } size_t BambooTracker::getBookmarkSize(int songNum) const { return mod_->getSong(songNum).getBookmarkSize(); } /*----- Key signature -----*/ void BambooTracker::addKeySignature(int songNum, KeySignature::Type key, int order, int step) { mod_->getSong(songNum).addKeySignature(key, order, step); } void BambooTracker::changeKeySignature(int songNum, int i, KeySignature::Type key, int order, int step) { mod_->getSong(songNum).changeKeySignature(i, key, order, step); } void BambooTracker::removeKeySignature(int songNum, int i) { mod_->getSong(songNum).removeKeySignature(i); } void BambooTracker::clearKeySignature(int songNum) { mod_->getSong(songNum).clearKeySignature(); } KeySignature BambooTracker::getKeySignature(int songNum, int i) const { return mod_->getSong(songNum).getKeySignature(i); } size_t BambooTracker::getKeySignatureSize(int songNum) const { return mod_->getSong(songNum).getKeySignatureSize(); } KeySignature::Type BambooTracker::searchKeySignatureAt(int songNum, int order, int step) const { return mod_->getSong(songNum).searchKeySignatureAt(order, step); } /*----- Track -----*/ void BambooTracker::setEffectDisplayWidth(int songNum, int trackNum, size_t w) { mod_->getSong(songNum).getTrack(trackNum).setEffectDisplayWidth(w); } size_t BambooTracker::getEffectDisplayWidth(int songNum, int trackNum) const { return mod_->getSong(songNum).getTrack(trackNum).getEffectDisplayWidth(); } void BambooTracker::setTrackVisibility(int songNum, int trackNum, bool visible) { mod_->getSong(songNum).getTrack(trackNum).setVisibility(visible); } bool BambooTracker::isVisibleTrack(int songNum, int trackNum) { return mod_->getSong(songNum).getTrack(trackNum).isVisible(); } /*----- Order -----*/ std::vector BambooTracker::getOrderData(int songNum, int orderNum) const { return mod_->getSong(songNum).getOrderData(orderNum); } void BambooTracker::setOrderPatternDigit(int songNum, int trackNum, int orderNum, int patternNum, bool secondEntry) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, patternNum, secondEntry)); } void BambooTracker::insertOrderBelow(int songNum, int orderNum) { comMan_.invoke(std::make_unique(mod_, songNum, orderNum)); } void BambooTracker::deleteOrder(int songNum, int orderNum) { comMan_.invoke(std::make_unique(mod_, songNum, orderNum)); } bool BambooTracker::pasteOrderCells(int songNum, int beginTrack, int beginOrder, const Vector2d& cells) { // Clip given cells to fit the size of pasted area. std::size_t w = std::min(cells.rowSize(), songStyle_.trackAttribs.size() - static_cast(beginTrack)); std::size_t h = std::min(cells.columnSize(), getOrderSize(songNum) - static_cast(beginOrder)); if (w == 0 || h == 0) return false; auto clipped = cells.clip(0, 0, h, w); if (!clipped.isValid() || clipped.empty()) return false; return comMan_.invoke(std::make_unique(mod_, songNum, beginTrack, beginOrder, clipped)); } void BambooTracker::duplicateOrder(int songNum, int orderNum) { comMan_.invoke(std::make_unique(mod_, songNum, orderNum)); } void BambooTracker::MoveOrder(int songNum, int orderNum, bool isUp) { comMan_.invoke(std::make_unique(mod_, songNum, orderNum, isUp)); } void BambooTracker::clonePatterns(int songNum, int beginOrder, int beginTrack, int endOrder, int endTrack) { comMan_.invoke(std::make_unique(mod_, songNum, beginOrder, beginTrack, endOrder, endTrack)); } void BambooTracker::cloneOrder(int songNum, int orderNum) { comMan_.invoke(std::make_unique(mod_, songNum, orderNum)); } size_t BambooTracker::getOrderSize(int songNum) const { return mod_->getSong(songNum).getOrderSize(); } bool BambooTracker::canAddNewOrder(int songNum) const { return mod_->getSong(songNum).canAddNewOrder(); } /*----- Pattern -----*/ int BambooTracker::getStepNoteNumber(int songNum, int trackNum, int orderNum, int stepNum) const { return mod_->getSong(songNum).getTrack(trackNum).getPatternFromOrderNumber(orderNum) .getStep(stepNum).getNoteNumber(); } void BambooTracker::setStepNote(int songNum, int trackNum, int orderNum, int stepNum, const Note& note, bool instMask, bool volMask) { int nn = Note(note).getNoteNumber(); SoundSource src = songStyle_.trackAttribs.at(static_cast(trackNum)).source; int in = -1; if (!instMask && curInstNum_ != -1 && (src == instMan_->getInstrumentSharedPtr(curInstNum_)->getSoundSource())) { in = curInstNum_; } bool fmReversed = (volFMReversed_ && src == SoundSource::FM); comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum, nn, instMask, in, volMask, curVolume_, fmReversed)); } void BambooTracker::setStepKeyOff(int songNum, int trackNum, int orderNum, int stepNum) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum)); } void BambooTracker::setStepKeyCut(int songNum, int trackNum, int orderNum, int stepNum) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum)); } void BambooTracker::setEchoBufferAccess(int songNum, int trackNum, int orderNum, int stepNum, int bufNum) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum, bufNum)); } void BambooTracker::eraseStepNote(int songNum, int trackNum, int orderNum, int stepNum) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum)); } int BambooTracker::getStepInstrument(int songNum, int trackNum, int orderNum, int stepNum) const { return mod_->getSong(songNum).getTrack(trackNum).getPatternFromOrderNumber(orderNum) .getStep(stepNum).getInstrumentNumber(); } void BambooTracker::setStepInstrumentDigit(int songNum, int trackNum, int orderNum, int stepNum, int instNum, bool secondEntry) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum, instNum, secondEntry)); } void BambooTracker::eraseStepInstrument(int songNum, int trackNum, int orderNum, int stepNum) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum)); } int BambooTracker::getStepVolume(int songNum, int trackNum, int orderNum, int stepNum) const { return mod_->getSong(songNum).getTrack(trackNum).getPatternFromOrderNumber(orderNum) .getStep(stepNum).getVolume(); } int BambooTracker::setStepVolumeDigit(int songNum, int trackNum, int orderNum, int stepNum, int volume, bool secondEntry) { bool fmReversed = (volFMReversed_ && songStyle_.trackAttribs.at(static_cast(trackNum)).source == SoundSource::FM); comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum, volume, fmReversed, secondEntry)); curVolume_ = mod_->getSong(songNum).getTrack(trackNum).getPatternFromOrderNumber(orderNum).getStep(stepNum).getVolume(); if (fmReversed) curVolume_ = effect_utils::reverseFmVolume(curVolume_); return curVolume_; } void BambooTracker::eraseStepVolume(int songNum, int trackNum, int orderNum, int stepNum) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum)); } std::string BambooTracker::getStepEffectID(int songNum, int trackNum, int orderNum, int stepNum, int n) const { return mod_->getSong(songNum).getTrack(trackNum).getPatternFromOrderNumber(orderNum) .getStep(stepNum).getEffectId(n); } void BambooTracker::setStepEffectIDCharacter(int songNum, int trackNum, int orderNum, int stepNum, int n, const std::string& id, bool fillValue00, bool secondEntry) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum, n, id, fillValue00, secondEntry)); } int BambooTracker::getStepEffectValue(int songNum, int trackNum, int orderNum, int stepNum, int n) const { return mod_->getSong(songNum).getTrack(trackNum).getPatternFromOrderNumber(orderNum) .getStep(stepNum).getEffectValue(n); } void BambooTracker::setStepEffectValueDigit(int songNum, int trackNum, int orderNum, int stepNum, int n, int value, EffectDisplayControl ctrl, bool secondEntry) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum, n, value, ctrl, secondEntry)); } void BambooTracker::eraseStepEffect(int songNum, int trackNum, int orderNum, int stepNum, int n) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum, n)); } void BambooTracker::eraseStepEffectValue(int songNum, int trackNum, int orderNum, int stepNum, int n) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum, n)); } void BambooTracker::insertStep(int songNum, int trackNum, int orderNum, int stepNum) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum)); } void BambooTracker::deletePreviousStep(int songNum, int trackNum, int orderNum, int stepNum) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum)); } namespace { Vector2d clipCellsToFitPastedArea( size_t trackCnt, size_t ptnSize, int beginTrack, int beginColmn, int beginStep, const Vector2d& cells, bool overflow) { std::size_t w = (trackCnt - static_cast(beginTrack) - 1) * Step::N_COLUMN + (Step::N_COLUMN - static_cast(beginColmn)); std::size_t h = ptnSize - static_cast(beginStep); std::size_t width = std::min(cells.columnSize(), w); std::size_t height = overflow ? cells.rowSize() : std::min(cells.rowSize(), h); return cells.clip(0, 0, height, width); } } bool BambooTracker::pastePatternCells(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, const Vector2d& cells, bool overflow) { const auto clipped = clipCellsToFitPastedArea(songStyle_.trackAttribs.size(), getPatternSizeFromOrderNumber(songNum, beginOrder), beginTrack, beginColmn, beginStep, cells, overflow); if (!clipped.isValid() || clipped.empty()) return false; return comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginColmn, beginOrder, beginStep, clipped)); } bool BambooTracker::pasteMixPatternCells(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, const Vector2d& cells, bool overflow) { const auto clipped = clipCellsToFitPastedArea(songStyle_.trackAttribs.size(), getPatternSizeFromOrderNumber(songNum, beginOrder), beginTrack, beginColmn, beginStep, cells, overflow); if (!clipped.isValid() || clipped.empty()) return false; return comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginColmn, beginOrder, beginStep, clipped)); } bool BambooTracker::pasteOverwritePatternCells(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, const Vector2d& cells, bool overflow) { const auto clipped = clipCellsToFitPastedArea(songStyle_.trackAttribs.size(), getPatternSizeFromOrderNumber(songNum, beginOrder), beginTrack, beginColmn, beginStep, cells, overflow); return comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginColmn, beginOrder, beginStep, clipped)); } bool BambooTracker::pasteInsertPatternCells(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, const Vector2d& cells) { const auto clipped = clipCellsToFitPastedArea(songStyle_.trackAttribs.size(), getPatternSizeFromOrderNumber(songNum, beginOrder), beginTrack, beginColmn, beginStep, cells, false); return comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginColmn, beginOrder, beginStep, clipped)); } bool BambooTracker::erasePatternCells(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColmn, int endStep) { return comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginColmn, beginOrder, beginStep, endTrack, endColmn, endStep)); } void BambooTracker::transposeNoteInPattern(int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep, int semitone) { comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginOrder, beginStep, endTrack, endStep, semitone)); } void BambooTracker::changeValuesInPattern(int songNum, int beginTrack, int beginColumn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep, int value) { comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginColumn, beginOrder, beginStep, endTrack, endColumn, endStep, value, volFMReversed_)); } void BambooTracker::expandPattern(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColmn, int endStep) { comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginColmn, beginOrder, beginStep, endTrack, endColmn, endStep)); } void BambooTracker::shrinkPattern(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColmn, int endStep) { comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginColmn, beginOrder, beginStep, endTrack, endColmn, endStep)); } void BambooTracker::interpolatePattern(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColmn, int endStep) { comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginColmn, beginOrder, beginStep, endTrack, endColmn, endStep)); } void BambooTracker::reversePattern(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColmn, int endStep) { comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginColmn, beginOrder, beginStep, endTrack, endColmn, endStep)); } void BambooTracker::replaceInstrumentInPattern(int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep, int newInstNum) { comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginOrder, beginStep, endTrack, endStep, newInstNum)); } size_t BambooTracker::getPatternSizeFromOrderNumber(int songNum, int orderNum) const { return mod_->getSong(songNum).getPatternSizeFromOrderNumber(orderNum); } void BambooTracker::setDefaultPatternSize(int songNum, size_t size) { mod_->getSong(songNum).setDefaultPatternSize(size); playback_->checkPlayPosition(static_cast(size)); } size_t BambooTracker::getDefaultPatternSize(int songNum) const { return mod_->getSong(songNum).getDefaultPatternSize(); } void BambooTracker::getOutputHistory(int16_t* container) { opnaCtrl_->getOutputHistory(container); } BambooTracker-0.6.5/BambooTracker/bamboo_tracker.hpp000066400000000000000000000646701476276175200224710ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include #include #include #include #include #include #include "jamming.hpp" #include "instrument.hpp" #include "instrument/sample_repeat.hpp" #include "module.hpp" #include "command/command_manager.hpp" #include "chip/real_chip_interface.hpp" #include "io/binary_container.hpp" #include "io/export_io.hpp" #include "io/wav_container.hpp" #include "bamboo_tracker_defs.hpp" #include "enum_hash.hpp" #include "vector_2d.hpp" class Configuration; enum class EffectDisplayControl; class AbstractBank; class OPNAController; class PlaybackManager; class TickCounter; class SampleRepeatRange; class BambooTracker { public: explicit BambooTracker(std::weak_ptr config); ~BambooTracker(); // Change confuguration void changeConfiguration(std::weak_ptr config); // Current octave void setCurrentOctave(int octave); int getCurrentOctave() const; // Current volume void setCurrentVolume(int volume); int getCurrentVolume() const; // Current track void setCurrentTrack(int num); TrackAttribute getCurrentTrackAttribute() const; // Current instrument void setCurrentInstrument(int n); int getCurrentInstrumentNumber() const; // Instrument edit void addInstrument(int num, InstrumentType type, const std::string& name); void removeInstrument(int num); std::unique_ptr getInstrument(int num); void cloneInstrument(int num, int refNum); void deepCloneInstrument(int num, int refNum); void swapInstruments(int a, int b, bool patternChange); void loadInstrument(io::BinaryContainer& container, const std::string& path, int instNum); void saveInstrument(io::BinaryContainer& container, int instNum); void importInstrument(const AbstractBank &bank, size_t index, int instNum); void exportInstruments(io::BinaryContainer& container, const std::vector& instNums); int findFirstFreeInstrumentNumber() const; void setInstrumentName(int num, const std::string& name); void clearAllInstrument(); std::vector getInstrumentIndices() const; std::vector getUnusedInstrumentIndices() const; void clearUnusedInstrumentProperties(); std::vector getInstrumentNames() const; //--- FM void setEnvelopeFMParameter(int envNum, FMEnvelopeParameter param, int value); void setEnvelopeFMOperatorEnable(int envNum, int opNum, bool enable); void setInstrumentFMEnvelope(int instNum, int envNum); std::multiset getEnvelopeFMUsers(int envNum) const; void setLFOFMParameter(int lfoNum, FMLFOParameter param, int value); void setInstrumentFMLFOEnabled(int instNum, bool enabled); void setInstrumentFMLFO(int instNum, int lfoNum); std::multiset getLFOFMUsers(int lfoNum) const; void addOperatorSequenceFMSequenceData(FMEnvelopeParameter param, int opSeqNum, int data); void removeOperatorSequenceFMSequenceData(FMEnvelopeParameter param, int opSeqNum); void setOperatorSequenceFMSequenceData(FMEnvelopeParameter param, int opSeqNum, int cnt, int data); void addOperatorSequenceFMLoop(FMEnvelopeParameter param, int opSeqNum, const InstrumentSequenceLoop& loop); void removeOperatorSequenceFMLoop(FMEnvelopeParameter param, int opSeqNum, int begin, int end); void changeOperatorSequenceFMLoop(FMEnvelopeParameter param, int opSeqNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearOperatorSequenceFMLoops(FMEnvelopeParameter param, int opSeqNum); void setOperatorSequenceFMRelease(FMEnvelopeParameter param, int opSeqNum, const InstrumentSequenceRelease& release); void setInstrumentFMOperatorSequence(int instNum, FMEnvelopeParameter param, int opSeqNum); void setInstrumentFMOperatorSequenceEnabled(int instNum, FMEnvelopeParameter param, bool enabled); std::multiset getOperatorSequenceFMUsers(FMEnvelopeParameter param, int opSeqNum) const; void setArpeggioFMType(int arpNum, SequenceType type); void addArpeggioFMSequenceData(int arpNum, int data); void removeArpeggioFMSequenceData(int arpNum); void setArpeggioFMSequenceData(int arpNum, int cnt, int data); void addArpeggioFMLoop(int arpNum, const InstrumentSequenceLoop& loop); void removeArpeggioFMLoop(int arpNum, int begin, int end); void changeArpeggioFMLoop(int arpNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearArpeggioFMLoops(int arpNum); void setArpeggioFMRelease(int arpNum, const InstrumentSequenceRelease& release); void setInstrumentFMArpeggio(int instNum, FMOperatorType op, int arpNum); void setInstrumentFMArpeggioEnabled(int instNum, FMOperatorType op, bool enabled); std::multiset getArpeggioFMUsers(int arpNum) const; void setPitchFMType(int ptNum, SequenceType type); void addPitchFMSequenceData(int ptNum, int data); void removePitchFMSequenceData(int ptNum); void setPitchFMSequenceData(int ptNum, int cnt, int data); void addPitchFMLoop(int ptNum, const InstrumentSequenceLoop& loop); void removePitchFMLoop(int ptNum, int begin, int end); void changePitchFMLoop(int ptNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearPitchFMLoops(int ptNum); void setPitchFMRelease(int ptNum, const InstrumentSequenceRelease& release); void setInstrumentFMPitch(int instNum, FMOperatorType op, int ptNum); void setInstrumentFMPitchEnabled(int instNum, FMOperatorType op, bool enabled); std::multiset getPitchFMUsers(int ptNum) const; void addPanFMSequenceData(int panNum, int data); void removePanFMSequenceData(int panNum); void setPanFMSequenceData(int panNum, int cnt, int data); void addPanFMLoop(int panNum, const InstrumentSequenceLoop& loop); void removePanFMLoop(int panNum, int begin, int end); void changePanFMLoop(int panNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearPanFMLoops(int panNum); void setPanFMRelease(int panNum, const InstrumentSequenceRelease& release); void setInstrumentFMPan(int instNum, int panNum); void setInstrumentFMPanEnabled(int instNum, bool enabled); std::multiset getPanFMUsers(int panNum) const; void setInstrumentFMEnvelopeResetEnabled(int instNum, FMOperatorType op, bool enabled); //--- SSG void addWaveformSSGSequenceData(int wfNum, const SSGWaveformUnit& data); void removeWaveformSSGSequenceData(int wfNum); void setWaveformSSGSequenceData(int wfNum, int cnt, const SSGWaveformUnit& data); void addWaveformSSGLoop(int wfNum, const InstrumentSequenceLoop& loop); void removeWaveformSSGLoop(int wfNum, int begin, int end); void changeWaveformSSGLoop(int wfNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearWaveformSSGLoops(int wfNum); void setWaveformSSGRelease(int wfNum, const InstrumentSequenceRelease& release); void setInstrumentSSGWaveform(int instNum, int wfNum); void setInstrumentSSGWaveformEnabled(int instNum, bool enabled); std::multiset getWaveformSSGUsers(int wfNum) const; void addToneNoiseSSGSequenceData(int tnNum, int data); void removeToneNoiseSSGSequenceData(int tnNum); void setToneNoiseSSGSequenceData(int tnNum, int cnt, int data); void addToneNoiseSSGLoop(int tnNum, const InstrumentSequenceLoop& loop); void removeToneNoiseSSGLoop(int tnNum, int begin, int end); void changeToneNoiseSSGLoop(int tnNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearToneNoiseSSGLoops(int tnNum); void setToneNoiseSSGRelease(int tnNum, const InstrumentSequenceRelease& release); void setInstrumentSSGToneNoise(int instNum, int tnNum); void setInstrumentSSGToneNoiseEnabled(int instNum, bool enabled); std::multiset getToneNoiseSSGUsers(int tnNum) const; void addEnvelopeSSGSequenceData(int envNum, const SSGEnvelopeUnit& data); void removeEnvelopeSSGSequenceData(int envNum); void setEnvelopeSSGSequenceData(int envNum, int cnt, const SSGEnvelopeUnit& data); void addEnvelopeSSGLoop(int envNum, const InstrumentSequenceLoop& loop); void removeEnvelopeSSGLoop(int envNum, int begin, int end); void changeEnvelopeSSGLoop(int envNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearEnvelopeSSGLoops(int envNum); void setEnvelopeSSGRelease(int envNum, const InstrumentSequenceRelease& release); void setInstrumentSSGEnvelope(int instNum, int envNum); void setInstrumentSSGEnvelopeEnabled(int instNum, bool enabled); std::multiset getEnvelopeSSGUsers(int envNum) const; void setArpeggioSSGType(int arpNum, SequenceType type); void addArpeggioSSGSequenceData(int arpNum, int data); void removeArpeggioSSGSequenceData(int arpNum); void setArpeggioSSGSequenceData(int arpNum, int cnt, int data); void addArpeggioSSGLoop(int arpNum, const InstrumentSequenceLoop& loop); void removeArpeggioSSGLoop(int arpNum, int begin, int end); void changeArpeggioSSGLoop(int arpNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearArpeggioSSGLoops(int arpNum); void setArpeggioSSGRelease(int arpNum, const InstrumentSequenceRelease& release); void setInstrumentSSGArpeggio(int instNum, int arpNum); void setInstrumentSSGArpeggioEnabled(int instNum, bool enabled); std::multiset getArpeggioSSGUsers(int arpNum) const; void setPitchSSGType(int ptNum, SequenceType type); void addPitchSSGSequenceData(int ptNum, int data); void removePitchSSGSequenceData(int ptNum); void setPitchSSGSequenceData(int ptNum, int cnt, int data); void addPitchSSGLoop(int ptNum, const InstrumentSequenceLoop& loop); void removePitchSSGLoop(int ptNum, int begin, int end); void changePitchSSGLoop(int ptNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearPitchSSGLoops(int ptNum); void setPitchSSGRelease(int ptNum, const InstrumentSequenceRelease& release); void setInstrumentSSGPitch(int instNum, int ptNum); void setInstrumentSSGPitchEnabled(int instNum, bool enabled); std::multiset getPitchSSGUsers(int ptNum) const; //--- ADPCM size_t getADPCMLimit() const; size_t getADPCMStoredSize() const; void setSampleADPCMRootKeyNumber(int sampNum, int n); int getSampleADPCMRootKeyNumber(int sampNum) const; void setSampleADPCMRootDeltaN(int sampNum, int dn); int getSampleADPCMRootDeltaN(int sampNum) const; void setSampleADPCMRepeatEnabled(int sampNum, bool enabled); bool getSampleADPCMRepeatEnabled(int sampNum) const; bool setSampleADPCMRepeatRange(int sampNum, const SampleRepeatRange& range); SampleRepeatRange getSampleADPCMRepeatRange(int sampNum) const; void storeSampleADPCMRawSample(int sampNum, const std::vector& sample); void storeSampleADPCMRawSample(int sampNum, std::vector&& sample); std::vector getSampleADPCMRawSample(int sampNum) const; void clearSampleADPCMRawSample(int sampNum); bool assignSampleADPCMRawSamples(); size_t getSampleADPCMStartAddress(int sampNum) const; size_t getSampleADPCMStopAddress(int sampNum) const; void setInstrumentADPCMSample(int instNum, int sampNum); std::multiset getSampleADPCMUsers(int sampNum) const; void addEnvelopeADPCMSequenceData(int envNum, int data); void removeEnvelopeADPCMSequenceData(int envNum); void setEnvelopeADPCMSequenceData(int envNum, int cnt, int data); void addEnvelopeADPCMLoop(int arpNum, const InstrumentSequenceLoop& loop); void removeEnvelopeADPCMLoop(int envNum, int begin, int end); void changeEnvelopeADPCMLoop(int envNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearEnvelopeADPCMLoops(int envNum); void setEnvelopeADPCMRelease(int arpNum, const InstrumentSequenceRelease& release); void setInstrumentADPCMEnvelope(int instNum, int envNum); void setInstrumentADPCMEnvelopeEnabled(int instNum, bool enabled); std::multiset getEnvelopeADPCMUsers(int envNum) const; void setArpeggioADPCMType(int arpNum, SequenceType type); void addArpeggioADPCMSequenceData(int arpNum, int data); void removeArpeggioADPCMSequenceData(int arpNum); void setArpeggioADPCMSequenceData(int arpNum, int cnt, int data); void addArpeggioADPCMLoop(int arpNum, const InstrumentSequenceLoop& loop); void removeArpeggioADPCMLoop(int arpNum, int begin, int end); void changeArpeggioADPCMLoop(int arpNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearArpeggioADPCMLoops(int arpNum); void setArpeggioADPCMRelease(int arpNum, const InstrumentSequenceRelease& release); void setInstrumentADPCMArpeggio(int instNum, int arpNum); void setInstrumentADPCMArpeggioEnabled(int instNum, bool enabled); std::multiset getArpeggioADPCMUsers(int arpNum) const; void setPitchADPCMType(int ptNum, SequenceType type); void addPitchADPCMSequenceData(int ptNum, int data); void removePitchADPCMSequenceData(int ptNum); void setPitchADPCMSequenceData(int ptNum, int cnt, int data); void addPitchADPCMLoop(int ptNum, const InstrumentSequenceLoop& loop); void removePitchADPCMLoop(int ptNum, int begin, int end); void changePitchADPCMLoop(int ptNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearPitchADPCMLoops(int ptNum); void setPitchADPCMRelease(int ptNum, const InstrumentSequenceRelease& release); void setInstrumentADPCMPitch(int instNum, int ptNum); void setInstrumentADPCMPitchEnabled(int instNum, bool enabled); std::multiset getPitchADPCMUsers(int ptNum) const; void addPanADPCMSequenceData(int panNum, int data); void removePanADPCMSequenceData(int panNum); void setPanADPCMSequenceData(int panNum, int cnt, int data); void addPanADPCMLoop(int panNum, const InstrumentSequenceLoop& loop); void removePanADPCMLoop(int panNum, int begin, int end); void changePanADPCMLoop(int panNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearPanADPCMLoops(int panNum); void setPanADPCMRelease(int panNum, const InstrumentSequenceRelease& release); void setInstrumentADPCMPan(int instNum, int panNum); void setInstrumentADPCMPanEnabled(int instNum, bool enabled); std::multiset getPanADPCMUsers(int panNum) const; //--- Drumkit void setInstrumentDrumkitSample(int instNum, int key, int sampNum); void setInstrumentDrumkitSampleEnabled(int instNum, int key, bool enabled); void setInstrumentDrumkitPitch(int instNum, int key, int pitch); void setInstrumentDrumkitPan(int instNum, int key, int pan); // Song edit void setCurrentSongNumber(int num); int getCurrentSongNumber() const; // Order edit int getCurrentOrderNumber() const; void setCurrentOrderNumber(int num); // Pattern edit int getCurrentStepNumber() const; void setCurrentStepNumber(int num); // Undo-Redo bool undo(); bool redo(); void clearCommandHistory(); // Jam mode void toggleJamMode(); bool isJamMode() const; void jamKeyOn(JamKey key, bool volumeSet); void jamKeyOn(int keyNum, bool volumeSet); void jamKeyOff(JamKey key); void jamKeyOff(int keyNum); void jamKeyOnForced(JamKey key, SoundSource src, bool volumeSet, std::shared_ptr inst = nullptr); void jamKeyOnForced(int keyNum, SoundSource src, bool volumeSet, std::shared_ptr inst = nullptr); void jamKeyOffForced(JamKey key, SoundSource src); void jamKeyOffForced(int keyNum, SoundSource src); void jamkeyOffAll(); bool assignADPCMBeforeForcedJamKeyOn(std::shared_ptr inst, std::unordered_map>& sampAddrs); // Play song void startPlaySong(); void startPlayFromStart(); void startPlayPattern(); void startPlayFromCurrentStep(); bool startPlayFromMarker(); void playStep(); void stopPlaySong(); bool isPlaySong() const; void setTrackMuteState(int trackNum, bool isMute); bool isMute(int trackNum); void setFollowPlay(bool isFollowed); bool isFollowPlay() const; int getPlayingOrderNumber() const; int getPlayingStepNumber() const; void setMarker(int order, int step); int getMarkerOrder() const; int getMarkerStep() const; // Export using ExportCancellCallback = std::function; bool exportToWav(io::WavContainer& container, int loopCnt, ExportCancellCallback checkFunc); bool exportToVgm(io::BinaryContainer& container, int target, bool gd3TagEnabled, const io::GD3Tag& tag, bool shouldSetMix, double gain, ExportCancellCallback checkFunc); bool exportToS98(io::BinaryContainer& container, int target, bool tagEnabled, const io::S98Tag& tag, int rate, ExportCancellCallback checkFunc); // Real chip interface void connectToRealChip(RealChipInterfaceType type, RealChipInterfaceGeneratorFunc* f = nullptr); RealChipInterfaceType getRealChipInterfaceType() const; bool hasConnectedToRealChip() const; // Stream events /// 0<: Tick /// 0: Step /// -1: Stop int streamCountUp(); /** * @brief getStreamSamples * @param container buffer where generated samples are stored. * @param nSamples number of needed samples. * @return true if sample generation is success, otherwise false. */ bool getStreamSamples(int16_t *container, size_t nSamples); void killSound(); // Stream details int getStreamRate() const; void setStreamRate(int rate); int getStreamDuration() const; void setStreamDuration(int duration); int getStreamTempo() const; int getStreamSpeed() const; bool getStreamGrooveEnabled() const; void setMasterVolume(int percentage); void setMasterVolumeFM(double dB); void setMasterVolumeSSG(double dB); // Module details /*----- Module -----*/ void makeNewModule(); void loadModule(io::BinaryContainer& container); void saveModule(io::BinaryContainer& container); void setModulePath(const std::string& path); std::string getModulePath() const; void setModuleTitle(const std::string& title); std::string getModuleTitle() const; void setModuleAuthor(const std::string& author); std::string getModuleAuthor() const; void setModuleCopyright(const std::string& copyright); std::string getModuleCopyright() const; void setModuleComment(const std::string& comment); std::string getModuleComment() const; void setModuleTickFrequency(unsigned int freq); unsigned int getModuleTickFrequency() const; void setModuleStepHighlight1Distance(size_t dist); size_t getModuleStepHighlight1Distance() const; void setModuleStepHighlight2Distance(size_t dist); size_t getModuleStepHighlight2Distance() const; void setModuleMixerType(MixerType type); MixerType getModuleMixerType() const; void setModuleCustomMixerFMLevel(double level); double getModuleCustomMixerFMLevel() const; void setModuleCustomMixerSSGLevel(double level); double getModuleCustomMixerSSGLevel() const; size_t getGrooveCount() const; void setGroove(int num, const std::vector& seq); void setGrooves(const std::vector>& seqs); std::vector getGroove(int num) const; void clearUnusedPatterns(); std::unordered_map replaceDuplicateInstrumentsInPatterns(); void clearUnusedADPCMSamples(); /*----- Song -----*/ void setSongTitle(int songNum, const std::string& title); std::string getSongTitle(int songNum) const; void setSongTempo(int songNum, int tempo); int getSongTempo(int songNum) const; void setSongGroove(int songNum, int groove); int getSongGroove(int songNum) const; void toggleTempoOrGrooveInSong(int songNum, bool isTempo); bool isUsedTempoInSong(int songNum) const; SongStyle getSongStyle(int songNum) const; void changeSongType(int songNum, SongType type); void setSongSpeed(int songNum, int speed); int getSongSpeed(int songNum) const; size_t getSongCount() const; void addSong(SongType songType, const std::string& title); void sortSongs(const std::vector& numbers); void transposeSong(int songNum, int semitones, const std::vector& excludeInsts); void swapTracks(int songNum, int track1, int track2); double estimateSongLength(int songNum) const; size_t getTotalStepCount(int songNum, size_t loopCnt) const; /*----- Bookmark -----*/ void addBookmark(int songNum, const std::string& name, int order, int step); void changeBookmark(int songNum, int i, const std::string& name, int order, int step); void removeBookmark(int songNum, int i); void clearBookmark(int songNum); void swapBookmarks(int songNum, int a, int b); void sortBookmarkByPosition(int songNum); void sortBookmarkByName(int songNum); Bookmark getBookmark(int songNum, int i) const; std::vector findBookmarks(int songNum, int order, int step); Bookmark getPreviousBookmark(int songNum, int order, int step); Bookmark getNextBookmark(int songNum, int order, int step); size_t getBookmarkSize(int songNum) const; /*----- Key signature -----*/ void addKeySignature(int songNum, KeySignature::Type key, int order, int step); void changeKeySignature(int songNum, int i, KeySignature::Type key, int order, int step); void removeKeySignature(int songNum, int i); void clearKeySignature(int songNum); KeySignature getKeySignature(int songNum, int i) const; size_t getKeySignatureSize(int songNum) const; KeySignature::Type searchKeySignatureAt(int songNum, int order, int step) const; /*----- Track -----*/ void setEffectDisplayWidth(int songNum, int trackNum, size_t w); size_t getEffectDisplayWidth(int songNum, int trackNum) const; void setTrackVisibility(int songNum, int trackNum, bool visible); bool isVisibleTrack(int songNum, int trackNum); /*----- Order -----*/ std::vector getOrderData(int songNum, int orderNum) const; void setOrderPatternDigit(int songNum, int trackNum, int orderNum, int patternNum, bool secondEntry); void insertOrderBelow(int songNum, int orderNum); void deleteOrder(int songNum, int orderNum); bool pasteOrderCells(int songNum, int beginTrack, int beginOrder, const Vector2d& cells); void duplicateOrder(int songNum, int orderNum); void MoveOrder(int songNum, int orderNum, bool isUp); void clonePatterns(int songNum, int beginOrder, int beginTrack, int endOrder, int endTrack); void cloneOrder(int songNum, int orderNum); size_t getOrderSize(int songNum) const; bool canAddNewOrder(int songNum) const; /*----- Pattern -----*/ int getStepNoteNumber(int songNum, int trackNum, int orderNum, int stepNum) const; void setStepNote(int songNum, int trackNum, int orderNum, int stepNum, const Note& note, bool instMask, bool volMask); void setStepKeyOff(int songNum, int trackNum, int orderNum, int stepNum); void setStepKeyCut(int songNum, int trackNum, int orderNum, int stepNum); void setEchoBufferAccess(int songNum, int trackNum, int orderNum, int stepNum, int bufNum); void eraseStepNote(int songNum, int trackNum, int orderNum, int stepNum); int getStepInstrument(int songNum, int trackNum, int orderNum, int stepNum) const; void setStepInstrumentDigit(int songNum, int trackNum, int orderNum, int stepNum, int instNum, bool secondEntry); void eraseStepInstrument(int songNum, int trackNum, int orderNum, int stepNum); int getStepVolume(int songNum, int trackNum, int orderNum, int stepNum) const; int setStepVolumeDigit(int songNum, int trackNum, int orderNum, int stepNum, int volume, bool secondEntry); void eraseStepVolume(int songNum, int trackNum, int orderNum, int stepNum); std::string getStepEffectID(int songNum, int trackNum, int orderNum, int stepNum, int n) const; void setStepEffectIDCharacter(int songNum, int trackNum, int orderNum, int stepNum, int n, const std::string& id, bool fillValue00, bool secondEntry); int getStepEffectValue(int songNum, int trackNum, int orderNum, int stepNum, int n) const; void setStepEffectValueDigit(int songNum, int trackNum, int orderNum, int stepNum, int n, int value, EffectDisplayControl ctrl, bool secondEntry); void eraseStepEffect(int songNum, int trackNum, int orderNum, int stepNum, int n); void eraseStepEffectValue(int songNum, int trackNum, int orderNum, int stepNum, int n); void deletePreviousStep(int songNum, int trackNum, int orderNum, int stepNum); void insertStep(int songNum, int trackNum, int orderNum, int stepNum); /// beginColumn /// 0: note /// 1: instrument /// 2: volume /// 3: effect ID /// 4: effect value bool pastePatternCells(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, const Vector2d& cells, bool overflow); bool pasteMixPatternCells(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, const Vector2d& cells, bool overflow); bool pasteOverwritePatternCells(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, const Vector2d& cells, bool overflow); bool pasteInsertPatternCells(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, const Vector2d& cells); bool erasePatternCells(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColmn, int endStep); void transposeNoteInPattern(int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep, int semitone); void changeValuesInPattern(int songNum, int beginTrack, int beginColumn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep, int value); void expandPattern(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColmn, int endStep); void shrinkPattern(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColmn, int endStep); void interpolatePattern(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColmn, int endStep); void reversePattern(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColmn, int endStep); void replaceInstrumentInPattern(int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep, int newInstNum); size_t getPatternSizeFromOrderNumber(int songNum, int orderNum) const; void setDefaultPatternSize(int songNum, size_t size); size_t getDefaultPatternSize(int songNum) const; /*----- Visual -----*/ void getOutputHistory(int16_t* container); private: CommandManager comMan_; std::shared_ptr instMan_; std::unique_ptr jamMan_; std::shared_ptr opnaCtrl_; std::shared_ptr tickCounter_; std::unique_ptr playback_; std::shared_ptr mod_; // Current status int curOctave_; // 0-7 int curSongNum_; SongStyle songStyle_; int curTrackNum_; int curOrderNum_, curStepNum_; /// -1: not set int curInstNum_; int curVolume_; bool volFMReversed_; std::unordered_map> muteState_; int mkOrder_, mkStep_; bool isFollowPlay_; bool storeOnlyUsedSamples_; // Module details void makeNewModule(bool withInstrument); // Jam mode void funcJamKeyOn(JamKey key, int keyNum, const TrackAttribute& attrib, bool volumeSet, std::shared_ptr inst = nullptr); void funcJamKeyOff(JamKey key, int keyNum, const TrackAttribute& attrib); // Play song void startPlay(); }; BambooTracker-0.6.5/BambooTracker/bamboo_tracker_defs.hpp000066400000000000000000000026441476276175200234630ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once enum class SoundSource : int { FM = 1, SSG = 2, RHYTHM = 4, ADPCM = 8 }; namespace bt_defs { constexpr int OUTPUT_HISTORY_SIZE = 1024; constexpr int NSTEP_FM_VOLUME = 0x80; constexpr int NSTEP_SSG_VOLUME = 0x10; constexpr int NSTEP_RHYTHM_VOLUME = 0x20; constexpr int NSTEP_ADPCM_VOLUME = 0x100; } BambooTracker-0.6.5/BambooTracker/chip/000077500000000000000000000000001476276175200177145ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/chip/2608_interface.hpp000066400000000000000000000012321476276175200230420ustar00rootroot00000000000000#pragma once #include #include "chip_defs.h" namespace chip { class Ym2608Interface { public: virtual ~Ym2608Interface() = default; virtual int startDevice(int clock, int& rateSsg, uint32_t dramSize) = 0; virtual void stopDevice() = 0; virtual void resetDevice() = 0; virtual void writeAddressToPortA(uint8_t address) = 0; virtual void writeAddressToPortB(uint8_t address) = 0; virtual void writeDataToPortA(uint8_t data) = 0; virtual void writeDataToPortB(uint8_t data) = 0; virtual uint8_t readData() = 0; virtual void updateStream(sample** outputs, int nSamples) = 0; virtual void updateSsgStream(sample** outputs, int nSamples) = 0; }; } BambooTracker-0.6.5/BambooTracker/chip/blip_buf/000077500000000000000000000000001476276175200214765ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/chip/blip_buf/blip_buf.c000066400000000000000000000240521476276175200234270ustar00rootroot00000000000000/* blip_buf 1.1.0. http://www.slack.net/~ant/ */ #include "blip_buf.h" #include #include #include #include /* Library Copyright (C) 2003-2009 Shay Green. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #if defined (BLARGG_TEST) && BLARGG_TEST #include "blargg_test.h" #endif /* Equivalent to ULONG_MAX >= 0xFFFFFFFF00000000. Avoids constants that don't fit in 32 bits. */ #if ULONG_MAX/0xFFFFFFFF > 0xFFFFFFFF typedef unsigned long fixed_t; enum { pre_shift = 32 }; #elif defined(ULLONG_MAX) typedef unsigned long long fixed_t; enum { pre_shift = 32 }; #else typedef unsigned fixed_t; enum { pre_shift = 0 }; #endif enum { time_bits = pre_shift + 20 }; static fixed_t const time_unit = (fixed_t) 1 << time_bits; enum { bass_shift = 9 }; /* affects high-pass filter breakpoint frequency */ enum { end_frame_extra = 2 }; /* allows deltas slightly after frame length */ enum { half_width = 8 }; enum { buf_extra = half_width*2 + end_frame_extra }; enum { phase_bits = 5 }; enum { phase_count = 1 << phase_bits }; enum { delta_bits = 15 }; enum { delta_unit = 1 << delta_bits }; enum { frac_bits = time_bits - pre_shift }; /* We could eliminate avail and encode whole samples in offset, but that would limit the total buffered samples to blip_max_frame. That could only be increased by decreasing time_bits, which would reduce resample ratio accuracy. */ /** Sample buffer that resamples to output rate and accumulates samples until they're read out */ struct blip_t { fixed_t factor; fixed_t offset; int avail; int size; int integrator; }; typedef int buf_t; /* probably not totally portable */ #define SAMPLES( buf ) ((buf_t*) ((buf) + 1)) /* Arithmetic (sign-preserving) right shift */ #define ARITH_SHIFT( n, shift ) \ ((n) >> (shift)) enum { max_sample = +32767 }; enum { min_sample = -32768 }; #define CLAMP( n ) \ {\ if ( (short) n != n )\ n = ARITH_SHIFT( n, 16 ) ^ max_sample;\ } static void check_assumptions( void ) { int n; #if INT_MAX < 0x7FFFFFFF || UINT_MAX < 0xFFFFFFFF #error "int must be at least 32 bits" #endif assert( (-3 >> 1) == -2 ); /* right shift must preserve sign */ n = max_sample * 2; CLAMP( n ); assert( n == max_sample ); n = min_sample * 2; CLAMP( n ); assert( n == min_sample ); assert( blip_max_ratio <= time_unit ); assert( blip_max_frame <= (fixed_t) -1 >> time_bits ); } blip_t* blip_new( int size ) { blip_t* m; assert( size >= 0 ); m = (blip_t*) malloc( sizeof *m + (size + buf_extra) * sizeof (buf_t) ); if ( m ) { m->factor = time_unit / blip_max_ratio; m->size = size; blip_clear( m ); check_assumptions(); } return m; } void blip_delete( blip_t* m ) { if ( m != NULL ) { /* Clear fields in case user tries to use after freeing */ memset( m, 0, sizeof *m ); free( m ); } } void blip_set_rates( blip_t* m, double clock_rate, double sample_rate ) { double factor = time_unit * sample_rate / clock_rate; m->factor = (fixed_t) factor; /* Fails if clock_rate exceeds maximum, relative to sample_rate */ assert( 0 <= factor - m->factor && factor - m->factor < 1 ); /* Avoid requiring math.h. Equivalent to m->factor = (int) ceil( factor ) */ if ( m->factor < factor ) m->factor++; /* At this point, factor is most likely rounded up, but could still have been rounded down in the floating-point calculation. */ } void blip_clear( blip_t* m ) { /* We could set offset to 0, factor/2, or factor-1. 0 is suitable if factor is rounded up. factor-1 is suitable if factor is rounded down. Since we don't know rounding direction, factor/2 accommodates either, with the slight loss of showing an error in half the time. Since for a 64-bit factor this is years, the halving isn't a problem. */ m->offset = m->factor / 2; m->avail = 0; m->integrator = 0; memset( SAMPLES( m ), 0, (m->size + buf_extra) * sizeof (buf_t) ); } int blip_clocks_needed( const blip_t* m, int samples ) { fixed_t needed; /* Fails if buffer can't hold that many more samples */ //assert( samples >= 0 && m->avail + samples <= m->size ); if (samples < 0 || m->size < m->avail + samples) { return -1; } needed = (fixed_t) samples * time_unit; if ( needed < m->offset ) return 0; return (needed - m->offset + m->factor - 1) / m->factor; } void blip_end_frame( blip_t* m, unsigned t ) { fixed_t off = t * m->factor + m->offset; m->avail += off >> time_bits; m->offset = off & (time_unit - 1); /* Fails if buffer size was exceeded */ assert( m->avail <= m->size ); } int blip_samples_avail( const blip_t* m ) { return m->avail; } static void remove_samples( blip_t* m, int count ) { buf_t* buf = SAMPLES( m ); int remain = m->avail + buf_extra - count; m->avail -= count; memmove( &buf [0], &buf [count], remain * sizeof buf [0] ); memset( &buf [remain], 0, count * sizeof buf [0] ); } int blip_read_samples( blip_t* m, short out [], int count, int stereo ) { assert( count >= 0 ); if ( count > m->avail ) count = m->avail; if ( count ) { int const step = stereo ? 2 : 1; buf_t const* in = SAMPLES( m ); buf_t const* end = in + count; int sum = m->integrator; do { /* Eliminate fraction */ int s = ARITH_SHIFT( sum, delta_bits ); sum += *in++; CLAMP( s ); *out = s; out += step; /* High-pass filter */ sum -= s << (delta_bits - bass_shift); } while ( in != end ); m->integrator = sum; remove_samples( m, count ); } return count; } /* Things that didn't help performance on x86: __attribute__((aligned(128))) #define short int restrict */ /* Sinc_Generator( 0.9, 0.55, 4.5 ) */ static short const bl_step [phase_count + 1] [half_width] = { { 43, -115, 350, -488, 1136, -914, 5861,21022}, { 44, -118, 348, -473, 1076, -799, 5274,21001}, { 45, -121, 344, -454, 1011, -677, 4706,20936}, { 46, -122, 336, -431, 942, -549, 4156,20829}, { 47, -123, 327, -404, 868, -418, 3629,20679}, { 47, -122, 316, -375, 792, -285, 3124,20488}, { 47, -120, 303, -344, 714, -151, 2644,20256}, { 46, -117, 289, -310, 634, -17, 2188,19985}, { 46, -114, 273, -275, 553, 117, 1758,19675}, { 44, -108, 255, -237, 471, 247, 1356,19327}, { 43, -103, 237, -199, 390, 373, 981,18944}, { 42, -98, 218, -160, 310, 495, 633,18527}, { 40, -91, 198, -121, 231, 611, 314,18078}, { 38, -84, 178, -81, 153, 722, 22,17599}, { 36, -76, 157, -43, 80, 824, -241,17092}, { 34, -68, 135, -3, 8, 919, -476,16558}, { 32, -61, 115, 34, -60, 1006, -683,16001}, { 29, -52, 94, 70, -123, 1083, -862,15422}, { 27, -44, 73, 106, -184, 1152,-1015,14824}, { 25, -36, 53, 139, -239, 1211,-1142,14210}, { 22, -27, 34, 170, -290, 1261,-1244,13582}, { 20, -20, 16, 199, -335, 1301,-1322,12942}, { 18, -12, -3, 226, -375, 1331,-1376,12293}, { 15, -4, -19, 250, -410, 1351,-1408,11638}, { 13, 3, -35, 272, -439, 1361,-1419,10979}, { 11, 9, -49, 292, -464, 1362,-1410,10319}, { 9, 16, -63, 309, -483, 1354,-1383, 9660}, { 7, 22, -75, 322, -496, 1337,-1339, 9005}, { 6, 26, -85, 333, -504, 1312,-1280, 8355}, { 4, 31, -94, 341, -507, 1278,-1205, 7713}, { 3, 35, -102, 347, -506, 1238,-1119, 7082}, { 1, 40, -110, 350, -499, 1190,-1021, 6464}, { 0, 43, -115, 350, -488, 1136, -914, 5861} }; /* Shifting by pre_shift allows calculation using unsigned int rather than possibly-wider fixed_t. On 32-bit platforms, this is likely more efficient. And by having pre_shift 32, a 32-bit platform can easily do the shift by simply ignoring the low half. */ void blip_add_delta( blip_t* m, unsigned time, int delta ) { unsigned fixed = (unsigned) ((time * m->factor + m->offset) >> pre_shift); buf_t* out = SAMPLES( m ) + m->avail + (fixed >> frac_bits); int const phase_shift = frac_bits - phase_bits; int phase = fixed >> phase_shift & (phase_count - 1); short const* in = bl_step [phase]; short const* rev = bl_step [phase_count - phase]; int interp = fixed >> (phase_shift - delta_bits) & (delta_unit - 1); int delta2 = (delta * interp) >> delta_bits; delta -= delta2; /* Fails if buffer size was exceeded */ assert( out <= &SAMPLES( m ) [m->size + end_frame_extra] ); out [0] += in[0]*delta + in[half_width+0]*delta2; out [1] += in[1]*delta + in[half_width+1]*delta2; out [2] += in[2]*delta + in[half_width+2]*delta2; out [3] += in[3]*delta + in[half_width+3]*delta2; out [4] += in[4]*delta + in[half_width+4]*delta2; out [5] += in[5]*delta + in[half_width+5]*delta2; out [6] += in[6]*delta + in[half_width+6]*delta2; out [7] += in[7]*delta + in[half_width+7]*delta2; in = rev; out [ 8] += in[7]*delta + in[7-half_width]*delta2; out [ 9] += in[6]*delta + in[6-half_width]*delta2; out [10] += in[5]*delta + in[5-half_width]*delta2; out [11] += in[4]*delta + in[4-half_width]*delta2; out [12] += in[3]*delta + in[3-half_width]*delta2; out [13] += in[2]*delta + in[2-half_width]*delta2; out [14] += in[1]*delta + in[1-half_width]*delta2; out [15] += in[0]*delta + in[0-half_width]*delta2; } void blip_add_delta_fast( blip_t* m, unsigned time, int delta ) { unsigned fixed = (unsigned) ((time * m->factor + m->offset) >> pre_shift); buf_t* out = SAMPLES( m ) + m->avail + (fixed >> frac_bits); int interp = fixed >> (frac_bits - delta_bits) & (delta_unit - 1); int delta2 = delta * interp; /* Fails if buffer size was exceeded */ assert( out <= &SAMPLES( m ) [m->size + end_frame_extra] ); out [7] += delta * delta_unit - delta2; out [8] += delta2; } BambooTracker-0.6.5/BambooTracker/chip/blip_buf/blip_buf.h000066400000000000000000000052351476276175200234360ustar00rootroot00000000000000/** \file Sample buffer that resamples from input clock rate to output sample rate */ /* blip_buf 1.1.0 */ #ifndef BLIP_BUF_H #define BLIP_BUF_H #ifdef __cplusplus extern "C" { #endif /** First parameter of most functions is blip_t*, or const blip_t* if nothing is changed. */ typedef struct blip_t blip_t; /** Creates new buffer that can hold at most sample_count samples. Sets rates so that there are blip_max_ratio clocks per sample. Returns pointer to new buffer, or NULL if insufficient memory. */ blip_t* blip_new( int sample_count ); /** Sets approximate input clock rate and output sample rate. For every clock_rate input clocks, approximately sample_rate samples are generated. */ void blip_set_rates( blip_t*, double clock_rate, double sample_rate ); enum { /** Maximum clock_rate/sample_rate ratio. For a given sample_rate, clock_rate must not be greater than sample_rate*blip_max_ratio. */ blip_max_ratio = 1 << 20 }; /** Clears entire buffer. Afterwards, blip_samples_avail() == 0. */ void blip_clear( blip_t* ); /** Adds positive/negative delta into buffer at specified clock time. */ void blip_add_delta( blip_t*, unsigned int clock_time, int delta ); /** Same as blip_add_delta(), but uses faster, lower-quality synthesis. */ void blip_add_delta_fast( blip_t*, unsigned int clock_time, int delta ); /** Length of time frame, in clocks, needed to make sample_count additional samples available. Returns negative value if buffer cannnot hold many more samples. */ int blip_clocks_needed( const blip_t*, int sample_count ); enum { /** Maximum number of samples that can be generated from one time frame. */ blip_max_frame = 4000 }; /** Makes input clocks before clock_duration available for reading as output samples. Also begins new time frame at clock_duration, so that clock time 0 in the new time frame specifies the same clock as clock_duration in the old time frame specified. Deltas can have been added slightly past clock_duration (up to however many clocks there are in two output samples). */ void blip_end_frame( blip_t*, unsigned int clock_duration ); /** Number of buffered samples available for reading. */ int blip_samples_avail( const blip_t* ); /** Reads and removes at most 'count' samples and writes them to 'out'. If 'stereo' is true, writes output to every other element of 'out', allowing easy interleaving of two buffers into a stereo sample stream. Outputs 16-bit signed samples. Returns number of samples actually read. */ int blip_read_samples( blip_t*, short out [], int count, int stereo ); /** Frees buffer. No effect if NULL is passed. */ void blip_delete( blip_t* ); /* Deprecated */ typedef blip_t blip_buffer_t; #ifdef __cplusplus } #endif #endif BambooTracker-0.6.5/BambooTracker/chip/blip_buf/blip_buf.txt000066400000000000000000000245021476276175200240240ustar00rootroot00000000000000blip_buf 1.1.0 -------------- Author : Shay Green Website : http://www.slack.net/~ant/ License : GNU Lesser General Public License (LGPL) Contents -------- * Overview * Buffer creation * Waveform generation * Time frames * Complex waveforms * Sample buffering * Thanks Overview -------- This library resamples audio waveforms from input clock rate to output sample rate. Usage follows this general pattern: * Create buffer with blip_new(). * Set clock rate and sample rate with blip_set_rates(). * Waveform generation loop: - Generate several clocks of waveform with blip_add_delta(). - End time frame with blip_end_frame(). - Read samples from buffer with blip_read_samples(). * Free buffer with blip_delete(). Buffer creation --------------- Before synthesis, a buffer must be created with blip_new(). Its size is the maximum number of unread samples it can hold. For most uses, this can be 1/10 the sample rate or less, since samples will usually be read out immediately after being generated. After the buffer is created, the input clock rate and output sample rate must be set with blip_set_rates(). This determines how many input clocks there are per second, and how many output samples are generated per second. If the compiler supports a 64-bit integer type, then the input-output ratio is stored very accurately. If the compiler only supports a 32-bit integer type, then the ratio is stored with only 20 fraction bits, so some ratios cannot be represented exactly (for example, sample rate=48000 and clock rate=48001). The ratio is internally rounded up, so there will never be fewer than 'sample rate' samples per second. Having too many per second is generally better than having too few. Waveform generation ------------------- Waveforms are generated at the input clock rate. Consider a simple square wave with 8 clocks per cycle (4 clocks high, 4 clocks low): |<-- 8 clocks ->| +5| ._._._._ ._._._._ ._._._._ ._._ | | | | | | | | Amp 0|._._._._ | | | | | | | | | | | | | -5| ._._._._ ._._._._ ._._._._ * . . . * . . . * . . . * . . . * . . . * . . . * . . . * . Time 0 4 8 12 16 20 24 28 The wave changes amplitude at time points 0, 4, 8, 12, 16, etc. The following generates the amplitude at every clock of above waveform at the input clock rate: int wave [30]; for ( int i = 4; i < 30; ++i ) { if ( i % 8 < 4 ) wave [i] = -5; else wave [i] = +5; } Without this library, the wave array would then need to be resampled from the input clock rate to the output sample rate. This library does this resampling internally, so it won't be discussed further; waveform generation code can focus entirely on the input clocks. Rather than specify the amplitude at every clock, this library merely needs to know the points where the amplitude CHANGES, referred to as a delta. The time of a delta is specified with a clock count. The deltas for this square wave are shown below the time points they occur at: +5| ._._._._ ._._._._ ._._._._ ._._ | | | | | | | | Amp 0|._._._._ | | | | | | | | | | | | | -5| ._._._._ ._._._._ ._._._._ * . . . * . . . * . . . * . . . * . . . * . . . * . . . * . Time 0 4 8 12 16 20 24 28 Delta +5 -10 +10 -10 +10 -10 +10 The following calls generate the above waveform: blip_add_delta( blip, 4, +5 ); blip_add_delta( blip, 8, -10 ); blip_add_delta( blip, 12, +10 ); blip_add_delta( blip, 16, -10 ); blip_add_delta( blip, 20, +10 ); blip_add_delta( blip, 24, -10 ); blip_add_delta( blip, 28, +10 ); In the examples above, the amplitudes are small for clarity. The 16-bit sample range is -32768 to +32767, so actual waveform amplitudes would need to be in the thousands to be audible (for example, -5000 to +5000). This library allows waveform generation code to pay NO attention to the output sample rate. It can focus ENTIRELY on the essence of the waveform: the points where its amplitude changes. Since these points can be efficiently generated in a loop, synthesis is efficient. Sound chip emulation code can be structured to allow full accuracy down to a single clock, with the emulated CPU being able to simply tell the sound chip to "emulate from wherever you left off, up to clock time T within the current time frame". Time frames ----------- Since time keeps increasing, if left unchecked, at some point it would overflow the range of an integer. This library's solution to the problem is to break waveform generation into time frames of moderate length. Clock counts within a time frame are thus relative to the beginning of the frame, where 0 is the beginning of the frame. When a time frame of length T is ended, what was at time T in the old time frame is now at time 0 in the new time frame. Breaking the above waveform into time frames of 10 clocks each looks like this: +5| ._._._._ ._._._._ ._._._._ ._._ | | | | | | | | Amp 0|._._._._ | | | | | | | | | | | | | -5| ._._._._ ._._._._ ._._._._ * . . . * . . . * . . . * . . . * . . . * . . . * . . . * . Time |0 4 8 | 2 6 |0 4 8 | | first time frame | second time frame | third time frame | |<--- 10 clocks --->|<--- 10 clocks --->|<--- 10 clocks --->| The following calls generate the above waveform. After they execute, the first 30 clocks of the waveform will have been resampled and be available as output samples for reading with blip_read_samples(). blip_add_delta( blip, 4, +5 ); blip_add_delta( blip, 8, -10 ); blip_end_frame( blip, 10 ); blip_add_delta( blip, 2, +10 ); blip_add_delta( blip, 6, -10 ); blip_end_frame( blip, 10 ); blip_add_delta( blip, 0, +10 ); blip_add_delta( blip, 4, -10 ); blip_add_delta( blip, 8, +10 ); blip_end_frame( blip, 10 ); ... Time frames can be a convenient length, and the length can vary from one frame to the next. Once a time frame is ended, the resulting output samples become available for reading immediately, and no more deltas can be added to it. There is a limit of about 4000 output samples per time frame. The number of clocks depends on the clock rate. At common sample rates, this allows time frames of at least 1/15 second, plenty for most uses. This limit allows increased resampling ratio accuracy. In an emulator, it is usually convenient to have audio time frames correspond to video frames, where the CPU's clock counter is reset at the beginning of each video frame and thus can be used directly as the relative clock counts for audio time frames. Complex waveforms ----------------- Any sort of waveform can be generated, not just a square wave. For example, a saw-like wave: +5| ._._._._ ._._._._ ._._ | | | | | | Amp 0|._._._._ | ._._._._ | ._._._._ | | | | | -5| ._._._._ ._._._._ * . . . * . . . * . . . * . . . * . . . * . . . * . . . * . Time 0 4 8 12 16 20 24 28 Delta +5 -10 +5 +5 -10 +5 +5 Code to generate above waveform: blip_add_delta( blip, 4, +5 ); blip_add_delta( blip, 8, -10 ); blip_add_delta( blip, 12, +5 ); blip_add_delta( blip, 16, +5 ); blip_add_delta( blip, 20, +10 ); blip_add_delta( blip, 24, +5 ); blip_add_delta( blip, 28, +5 ); Similarly, multiple waveforms can be added within a time frame without problem. It doesn't matter what order they're added, because all the library needs are the deltas. The synthesis code doesn't need to know all the waveforms at once either; it can calculate and add the deltas for each waveform individually. Deltas don't need to be added in chronological order either. Sample buffering ---------------- Sample buffering is very flexible. Once a time frame is ended, the resampled waveforms become output samples that are immediately made available for reading with blip_read_samples(). They don't have to be read immediately; they can be allowed to accumulate in the buffer, with each time frame appending more samples to the buffer. When reading, some or all of the samples in can be read out, with the remaining unread samples staying in the buffer for later. Usually a program will immediately read all available samples after ending a time frame and play them immediately. In some systems, a program needs samples in fixed-length blocks; in that case, it would keep generating time frames until some number of samples are available, then read only that many, even if slightly more were available in the buffer. In some systems, one wants to run waveform generation for exactly the number of clocks necessary to generate some desired number of output samples, and no more. In that case, use blip_clocks_needed( blip, N ) to find out how many clocks are needed to generate N additional samples. Ending a time frame with this value will result in exactly N more samples becoming available for reading. Thanks ------ Thanks to Jsr (FamiTracker author), the Mednafen team (multi-system emulator), ShizZie (Nhes GMB author), Marcel van Tongeren, Luke Molnar (UberNES author), Fredrick Meunier (Fuse contributor) for using and giving feedback for another similar library. Thanks to Disch for his interest and discussions about the synthesis algorithm itself, and for writing his own implementation of it (Schpune) rather than just using mine. Thanks to Xodnizel for Festalon, whose sound quality got me interested in video game sound emulation in the first place, and where I first came up with the algorithm while optimizing its brute-force filter. -- Shay Green BambooTracker-0.6.5/BambooTracker/chip/blip_buf/changes.txt000066400000000000000000000020721476276175200236500ustar00rootroot00000000000000Blip_buf Change Log ------------------- blip_buf 1.1.0 (2009-10-06) -------------- * Changed library name to blip_buf, to make it easier to find uses of on the Web. * Increased internal input-output ratio accuracy greatly when compiler supports 64-bit integer type. * Renamed blip_buffer_t to blip_t, for consistency with function names. Old name is deprecated. * Reduced high-pass so that a bit more bass comes through. * Made header file minimally Doxygen-compatible. * Added unit test suite. * Added assertions to catch more precondition violations (negative values, etc.) * blip_new() sets default rate to blip_max_ratio clocks per sample. * blip_delete() can now accept NULL, as originally claimed. * blip_clocks_needed() now properly returns zero when passed zero. * blip_add_delta_fast() now adds delta at same place blip_add_delta()'s center of delta would be, rather than one sample later as before. * blip_add_delta() and blip_add_delta_fast() allow adding slightly past buffer size, as originally claimed. blip 1.0.0 (2008-12-08) ---------- * First release BambooTracker-0.6.5/BambooTracker/chip/blip_buf/license.txt000066400000000000000000000635041476276175200236710ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! BambooTracker-0.6.5/BambooTracker/chip/blip_buf/readme.txt000066400000000000000000000047131476276175200235010ustar00rootroot00000000000000blip_buf 1.1.0: Band-Limited Audio Buffer ----------------------------------------- Blip_buf is a small waveform synthesis library meant for use in classic video game sound chip emulation. It greatly simplifies sound chip emulation code by handling all the details of resampling. The emulator merely sets the input clock rate and output sample rate, adds waveforms by specifying the clock times where their amplitude changes, then reads the resulting output samples. For a more full-features synthesis library, get Blip_Buffer. * Simple C interface and usage. * Several code examples, including simple sound chip emulator. * Uses fast, high-quality band-limited resampling algorithm (BLEP). * Output is low-pass and high-pass filtered and clamped to 16-bit range. * Supports mono, stereo, and multi-channel synthesis. Author : Shay Green Website : http://www.slack.net/~ant/ License : GNU Lesser General Public License (LGPL) Language: C Getting Started --------------- Build the demos by typing "make" at the command-line. If that doesn't work, manually build a program from demo.c, blip_buf.c, and wave_writer.c. Run "demo", which should record a square wave sweep to "out.wav". The others demo advanced topics, and their source code can be used as a starting point for experimentation (after making any changes, just type "make" to do any necessary recompilation). To build the SDL demo, type "make demo_sdl" at the command-line, or manually build a program from demo_sdl.c, blip_buf.c, and the SDL multimedia library. See blip_buf.h for reference and blip_buf.txt for documentation. Files ----- readme.txt Essential information blip_buf.txt Library documentation license.txt GNU Lesser General Public License makefile Builds demos (type "make" at command-line) demo.c Generates square wave sweep demo_stereo.c Generates stereo sound using two blip buffers demo_fixed.c Works in fixed-point time rather than clocks demo_sdl.c Plays sound live using SDL multimedia library demo_chip.c Emulates sound hardware and plays back log.txt demo_log.txt Log of sound hardware writes for demo_chip.c wave_writer.h Simple wave sound file writer used by demos wave_writer.c blip_buf.h Library header and reference (Doxygen-enabled) blip_buf.c source code tests/ Unit tests (build with "make test") -- Shay Green BambooTracker-0.6.5/BambooTracker/chip/c86ctl/000077500000000000000000000000001476276175200210175ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/chip/c86ctl/c86ctl.h000066400000000000000000000145351476276175200223030ustar00rootroot00000000000000/*** c86ctl Copyright (c) 2009-2012, honet. All rights reserved. This software is licensed under the BSD license. honet.kk(at)gmail.com */ #ifndef _C86CTL_H #define _C86CTL_H #include #include "cbus_boardtype.h" #ifdef __cplusplus namespace c86ctl{ #endif /*----------------------------------------------------------------------------*/ /* 定数定義 */ /*----------------------------------------------------------------------------*/ #define C86CTL_ERR_NONE 0 #define C86CTL_ERR_UNKNOWN -1 #define C86CTL_ERR_INVALID_PARAM -2 #define C86CTL_ERR_UNSUPPORTED -3 #define C86CTL_ERR_NODEVICE -1000 #define C86CTL_ERR_NOT_IMPLEMENTED -9999 enum ChipType { CHIP_UNKNOWN = 0x0000, CHIP_OPNA = 0x00001, CHIP_YM2608 = 0x00001, CHIP_YM2608NOADPCM = 0x10001, CHIP_OPM = 0x00002, CHIP_YM2151 = 0x00002, CHIP_OPN3L = 0x00003, CHIP_YMF288 = 0x00003, CHIP_OPL3 = 0x00004, CHIP_YMF262 = 0x00004, CHIP_OPLL = 0x00005, CHIP_YM2413 = 0x00005, CHIP_SN76489 = 0x00006, CHIP_SN76496 = 0x10006, CHIP_AY38910 = 0x00007, CHIP_YM2149 = 0x10007, CHIP_YM2203 = 0x00008, CHIP_YM2612 = 0x00009, CHIP_YM3526 = 0x0000a, CHIP_YM3812 = 0x0000b, CHIP_YMF271 = 0x0000c, CHIP_YMF278B = 0x0000d, CHIP_YMZ280B = 0x0000e, CHIP_YMF297 = 0x0000f, CHIP_YMF297_OPN3L = 0x0000f, CHIP_YMF297_OPL3 = 0x2000f, CHIP_YM2610B = 0x00010, CHIP_Y8950 = 0x00020, CHIP_Y8950ADPCM = 0x10020, CHIP_YM3438 = 0x00021, CHIP_TMS3631RI104 = 0x00023, }; /*----------------------------------------------------------------------------*/ /* 構造体定義 */ /*----------------------------------------------------------------------------*/ struct Devinfo{ char Devname[16]; char Rev; char Serial[15]; }; /*----------------------------------------------------------------------------*/ /* Interface定義 */ /*----------------------------------------------------------------------------*/ // IRealChipBase {5C457918-E66D-4AC1-8CB5-B91C4704DF79} static const GUID IID_IRealChipBase = { 0x5c457918, 0xe66d, 0x4ac1, { 0x8c, 0xb5, 0xb9, 0x1c, 0x47, 0x4, 0xdf, 0x79 } }; interface IRealChipBase : public IUnknown { virtual int __stdcall initialize(void) = 0; virtual int __stdcall deinitialize(void) = 0; virtual int __stdcall getNumberOfChip(void) = 0; virtual HRESULT __stdcall getChipInterface( int id, REFIID riid, void** ppi ) = 0; }; // --------------------------------------- // DEPRECATED. use IRealChip3 instead. // IRealChip {F959C007-6B4D-46F3-BB60-9B0897C7E642} static const GUID IID_IRealChip = { 0xf959c007, 0x6b4d, 0x46f3, { 0xbb, 0x60, 0x9b, 0x8, 0x97, 0xc7, 0xe6, 0x42 } }; interface IRealChip : public IUnknown { public: virtual int __stdcall reset(void) = 0; virtual void __stdcall out(UINT addr, UCHAR data) = 0; virtual UCHAR __stdcall in(UINT addr) = 0; }; // DEPRECATED. use IRealChip3 instead. // IRealChip2 {BEFA830A-0DF3-46E4-A79E-FABB78E80357} static const GUID IID_IRealChip2 = { 0xbefa830a, 0xdf3, 0x46e4, { 0xa7, 0x9e, 0xfa, 0xbb, 0x78, 0xe8, 0x3, 0x57 } }; interface IRealChip2 : public IRealChip { //IRealChipより継承 //public: // virtual int __stdcall reset(void) = 0; // virtual void __stdcall out(UINT addr, UCHAR data) = 0; // virtual UCHAR __stdcall in(UINT addr) = 0; public: virtual int __stdcall getChipStatus(UINT addr, UCHAR* status) = 0; virtual void __stdcall directOut(UINT addr, UCHAR data) = 0; }; // IRealChip3 {761DB10B-2432-4747-AC75-0EA6D9336797} static const GUID IID_IRealChip3 = { 0x761db10b, 0x2432, 0x4747, { 0xac, 0x75, 0xe, 0xa6, 0xd9, 0x33, 0x67, 0x97 } }; interface IRealChip3 : public IRealChip2 { //IRealChip2より継承 //public: // virtual int __stdcall reset(void) = 0; // virtual void __stdcall out(UINT addr, UCHAR data) = 0; // virtual UCHAR __stdcall in(UINT addr) = 0; //public: // virtual int __stdcall getChipStatus(UINT addr, UCHAR *status) = 0; // virtual void __stdcall directOut(UINT addr, UCHAR data) = 0; public: virtual int __stdcall getChipType(enum ChipType* type) = 0; }; // --------------------------------------- // DEPRECATED. use IGimic2 instead. // IGimic {175C7DA0-8AA5-4173-96DA-BB43B8EB8F17} static const GUID IID_IGimic = { 0x175c7da0, 0x8aa5, 0x4173, { 0x96, 0xda, 0xbb, 0x43, 0xb8, 0xeb, 0x8f, 0x17 } }; interface IGimic : public IUnknown { virtual int __stdcall getFWVer(UINT* major, UINT* minor, UINT* revision, UINT* build) = 0; virtual int __stdcall getMBInfo(struct Devinfo* info) = 0; virtual int __stdcall getModuleInfo(struct Devinfo* info) = 0; virtual int __stdcall setSSGVolume(UCHAR vol) = 0; virtual int __stdcall getSSGVolume(UCHAR* vol) = 0; virtual int __stdcall setPLLClock(UINT clock) = 0; virtual int __stdcall getPLLClock(UINT* clock) = 0; }; // IGimic2 {47141A01-15F5-4BF5-9554-CA7AACD54BB8} static const GUID IID_IGimic2 = { 0x47141a01, 0x15f5, 0x4bf5, { 0x95, 0x54, 0xca, 0x7a, 0xac, 0xd5, 0x4b, 0xb8 } }; interface IGimic2 : public IGimic { virtual int __stdcall getModuleType( enum ChipType *type ) = 0; }; // --------------------------------------- // 仮定義です。まだ使えません・・・ごめん。 // IC86Usb {312481E2-A93C-4A2F-87CA-CE3AC1096ED5} static const GUID IID_IC86BOX = { 0x312481e2, 0xa93c, 0x4a2f, { 0x87, 0xca, 0xce, 0x3a, 0xc1, 0x9, 0x6e, 0xd5 } }; interface IC86Box : public IUnknown { virtual int __stdcall getFWVer(UINT *major, UINT *minor, UINT *revision, UINT *build) = 0; virtual int __stdcall getBoardType(CBUS_BOARD_TYPE *type) = 0; virtual int __stdcall getSlotIndex() = 0; virtual int __stdcall writeBoardControl(UINT index, UINT val) = 0; }; /*----------------------------------------------------------------------------*/ /* 公開関数定義 */ /*----------------------------------------------------------------------------*/ #ifdef __cplusplus extern "C" { #endif HRESULT WINAPI CreateInstance( REFIID riid, void** ppi ); int WINAPI c86ctl_initialize(void); // DEPRECATED int WINAPI c86ctl_deinitialize(void); // DEPRECATED int WINAPI c86ctl_reset(void); // DEPRECATED void WINAPI c86ctl_out( UINT addr, UCHAR data ); // DEPRECATED UCHAR WINAPI c86ctl_in( UINT addr ); // DEPRECATED #ifdef __cplusplus } } #endif #endif BambooTracker-0.6.5/BambooTracker/chip/c86ctl/c86ctl_wrapper.cpp000066400000000000000000000063701476276175200243740ustar00rootroot00000000000000/* * Copyright (C) 2020-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "c86ctl_wrapper.hpp" #include #include "c86ctl.h" C86ctl::C86ctl() : base_(nullptr), rc_(nullptr), gm_(nullptr) {} C86ctl::~C86ctl() { if (gm_) gm_->Release(); if (rc_) rc_->Release(); if (base_) { base_->deinitialize(); base_->Release(); } } bool C86ctl::createInstance(RealChipInterfaceGeneratorFunc* f) { // Create instance auto tmpFunc = f ? reinterpret_cast((*f)()) : nullptr; // Avoid MSVC C4191 if (f) delete f; if (auto createInstance = reinterpret_cast(tmpFunc)) { c86ctl::IRealChipBase* base = nullptr; createInstance(c86ctl::IID_IRealChipBase, reinterpret_cast(&base)); if (base_) { base_->Release(); base_ = nullptr; if (!base) return false; } base_ = base; base_->initialize(); int nChip = base_->getNumberOfChip(); for (int i = 0; i < nChip; ++i) { c86ctl::IRealChip2* rc = nullptr; base_->getChipInterface(i, c86ctl::IID_IRealChip, reinterpret_cast(&rc)); if (rc) { if (rc_) rc_->Release(); rc_ = rc; rc_->reset(); c86ctl::IGimic2* gm = nullptr; if (rc_->QueryInterface(c86ctl::IID_IGimic2, reinterpret_cast(&gm)) == S_OK) { c86ctl::ChipType type; gm->getModuleType(&type); switch (type) { case c86ctl::CHIP_YM2608: case c86ctl::CHIP_YM2608NOADPCM: if (gm_) gm_->Release(); gm_ = gm; return true; default: break; } gm->Release(); } rc_->Release(); rc_ = nullptr; } } } else { // Clear interfaces if (!base_) return false; rc_->reset(); gm_->Release(); gm_ = nullptr; rc_->Release(); rc_ = nullptr; } base_->deinitialize(); base_->Release(); base_ = nullptr; return false; } bool C86ctl::hasConnected() const { return (base_ != nullptr); } void C86ctl::reset() { if (rc_) rc_->reset(); } void C86ctl::setRegister(uint32_t addr, uint8_t data) { if (rc_) rc_->out(addr, data); } void C86ctl::setSSGVolume(double dB) { if (gm_) { // NOTE: estimate SSG volume roughly uint8_t vol = static_cast(std::round((dB < -3.0) ? (2.5 * dB + 45.5) : (7. * dB + 59.))); gm_->setSSGVolume(vol); } } BambooTracker-0.6.5/BambooTracker/chip/c86ctl/c86ctl_wrapper.hpp000066400000000000000000000033621476276175200243770ustar00rootroot00000000000000/* * Copyright (C) 2020-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include "../real_chip_interface.hpp" namespace c86ctl { struct IRealChipBase; struct IRealChip; struct IGimic2; } class C86ctl final : public SimpleRealChipInterface { public: C86ctl(); ~C86ctl() override; bool createInstance(RealChipInterfaceGeneratorFunc* f) override; RealChipInterfaceType getType() const override { return RealChipInterfaceType::C86CTL; } bool hasConnected() const override; void reset() override; void setRegister(uint32_t addr, uint8_t data) override; void setSSGVolume(double dB) override; private: c86ctl::IRealChipBase* base_; c86ctl::IRealChip* rc_; c86ctl::IGimic2* gm_; }; BambooTracker-0.6.5/BambooTracker/chip/c86ctl/cbus_boardtype.h000066400000000000000000000241051476276175200241770ustar00rootroot00000000000000/*** c86ctl Copyright (c) 2009-2012, honet. All rights reserved. This software is licensed under the BSD license. honet.kk(at)gmail.com */ #ifndef CBUS_BOARDTYPE_H__ #define CBUS_BOARDTYPE_H__ // CBUSボード種別定義 // 上位16bit: アドレス種別などバリエーション // 下位16bit: ボード種別 (うち下位4bitは搭載チップによる大雑把な分類) typedef enum { // 何だか分からない or 何も刺さっていない CBUS_BOARD_UNKNOWN = 0x0, // PSG系 ========================================================= // (未対応) NEC PC-9801-14 // アドレスデコーダ: 14bit CBUS_BOARD_14 = 0x00001, // (未対応) システムサコム AMD-98 // アドレスデコーダ: 16bit(未検証) // SOUNDID(0xA460) : 無し // IDADDR: 0x00f0, 00xx x000 1101 xaaa (0x00d0~0x0df) CBUS_BOARD_AMD98 = 0x00011, // YM2203(OPN)系 ================================================= // NEC PC-9801-26, 26K // アドレスデコーダ: 12bit // SOUNDID(0xA460) : 無し // IOADDR: 0188h/018Ah CBUS_BOARD_26 = 0x00002, // (未テスト) SNE Sound Orchestra : YM2203, YM3812 // アドレスデコーダ: 不明 // SOUNDID(0xA460) : 無し // IOADDR(OPN) : 0188h/018Ah or 0088h/008Ah // IOADDR(YM3812): 018Ch/018Eh or 008Ch/008Eh CBUS_BOARD_SOUND_ORCHESTRA = 0x00012, CBUS_BOARD_SOUND_ORCHESTRA_0188H = 0x00012, CBUS_BOARD_SOUND_ORCHESTRA_0088H = 0x10012, // (未テスト) SNE Sound Orchestra L : YM2203, YM3812 // アドレスデコーダ: 12bit // SOUNDID(0xA460) : 無し // IOADDR(OPN) : 0188h/018Ah or 0088h/008Ah // IOADDR(Y8950): 018Ch/018Eh or 008Ch/008Eh CBUS_BOARD_SOUND_ORCHESTRA_L = 0x00022, CBUS_BOARD_SOUND_ORCHESTRA_L_0188H = 0x00022, CBUS_BOARD_SOUND_ORCHESTRA_L_0088H = 0x10022, // (未テスト) SNE Sound Orchestra V : YM2203, Y8950(w/ADPCM-RAM) // アドレスデコーダ: 12bit // SOUNDID(0xA460) : 無し // IOADDR(OPN) : 0188h/018Ah or 0088h/008Ah // IOADDR(Y8950): 018Ch/018Eh or 008Ch/008Eh CBUS_BOARD_SOUND_ORCHESTRA_V = 0x00032, CBUS_BOARD_SOUND_ORCHESTRA_V_0188H = 0x00032, CBUS_BOARD_SOUND_ORCHESTRA_V_0088H = 0x10032, // (未テスト) SNE Sound Orchestra VS : YM2203, Y8950(w/ADPCM-RAM) // アドレスデコーダ: 12bit // SOUNDID(0xA460) : 無し // IOADDR(OPN) : 0188h/018Ah or 0088h/008Ah // IOADDR(Y8950): 018Ch/018Eh or 008Ch/008Eh CBUS_BOARD_SOUND_ORCHESTRA_VS = 0x00042, CBUS_BOARD_SOUND_ORCHESTRA_VS_0188H = 0x00042, CBUS_BOARD_SOUND_ORCHESTRA_VS_0088H = 0x10042, // (未テスト) SNE Sound Orchestra LS : YM2203, Y8950 // アドレスデコーダ: 不明 // SOUNDID(0xA460) : 無し // IOADDR(OPN): 0188h/018Ah or 0088h/008Ah // IOADDR(Y8950): 018Ch/018Eh or 008Ch/008Eh CBUS_BOARD_SOUND_ORCHESTRA_LS = 0x00052, CBUS_BOARD_SOUND_ORCHESTRA_LS_0188H = 0x00052, CBUS_BOARD_SOUND_ORCHESTRA_LS_0088H = 0x10052, // (未テスト) SNE Sound Orchestra MATE : YM2203, Y8950 CBUS_BOARD_SOUND_ORCHESTRA_MATE = 0x00062, // (未テスト) SNE Multimedia Orchestra : YM2203, YM262M CBUS_BOARD_MULTIMEDIA_ORCHESTRA = 0x00072, // (未テスト) SNE Littele Orchestra : YM2203 // アドレスデコーダ: 不明 // SOUNDID(0xA460) : 無し // IOADDR: 0088h/008Ah/008Ch/008Eh or 0188h/018Ah/018Ch/018Eh CBUS_BOARD_LITTLE_ORCHESTRA = 0x00082, CBUS_BOARD_LITTLE_ORCHESTRA_0188H = 0x00082, CBUS_BOARD_LITTLE_ORCHESTRA_0088H = 0x10082, // (未テスト) SNE Littele Orchestra L : YM2203 // アドレスデコーダ: 不明 // SOUNDID(0xA460) : 無し // IOADDR: 0088h/008Ah/008Ch/008Eh or 0188h/018Ah/018Ch/018Eh CBUS_BOARD_LITTLE_ORCHESTRA_L = 0x00092, CBUS_BOARD_LITTLE_ORCHESTRA_L_0188H = 0x00092, CBUS_BOARD_LITTLE_ORCHESTRA_L_0088H = 0x01092, // (未テスト) SNE Littele Orchestra RS : YM2203 // アドレスデコーダ: 不明 // SOUNDID(0xA460) : 無し // IOADDR: 0088h/008Ah/008Ch/008Eh or 0188h/018Ah/018Ch/018Eh CBUS_BOARD_LITTLE_ORCHESTRA_RS = 0x000a2, CBUS_BOARD_LITTLE_ORCHESTRA_RS_0188H = 0x000a2, CBUS_BOARD_LITTLE_ORCHESTRA_RS_0088H = 0x100a2, // (未テスト) SNE Littele Orchestra LS : YM2203 // アドレスデコーダ: 不明 // SOUNDID(0xA460) : 無し // IOADDR: 0088h/008Ah/008Ch/008Eh or 0188h/018Ah/018Ch/018Eh CBUS_BOARD_LITTLE_ORCHESTRA_LS = 0x000b2, CBUS_BOARD_LITTLE_ORCHESTRA_LS_0188H = 0x000b2, CBUS_BOARD_LITTLE_ORCHESTRA_LS_0088H = 0x100b2, // (未テスト) SNE Littele Orchestra SS : YM2203 CBUS_BOARD_LITTLE_ORCHESTRA_SS = 0x000c2, // (未テスト) SNE Littele Orchestra MATE : YM2203 CBUS_BOARD_LITTLE_ORCHESTRA_MATE = 0x000d2, // (未テスト) SNE Littele Orchestra FELLOW : YM2203 CBUS_BOARD_LITTLE_ORCHESTRA_FELLOW = 0x000e2, // (未テスト) SNE JOY-2 : YM2203 CBUS_BOARD_JOY2 = 0x000f2, // (未テスト) SNE SOUND GRANPRI : YM2203 CBUS_BOARD_SOUND_GRANPRI = 0x00102, // (未テスト) 東京ニーズ TN-F3FM : YM2203C CBUS_BOARD_TN_F3FM = 0x00112, // YM2608(OPNA)系 ================================================= // NEC PC-9801-73 // アドレスデコーダ: 不明(16~20bit) // SOUNDID(0xA460) : 0x20 / 0x30 (86互換) // IOADDR: 0188h/018Ah/018Ch/018Eh or 0288h/028Ah/028Ch/028Eh CBUS_BOARD_73 = 0x00003, CBUS_BOARD_73_0188H = 0x00003, CBUS_BOARD_73_0288H = 0x10003, // NEC PC-9801-86 // アドレスデコーダ: 不明(16~20bit) // SOUNDID(0xA460) : 0x40 / 0x50 (86互換) // IOADDR: 0188h/018Ah/018Ch/018Eh or 0288h/028Ah/028Ch/028Eh CBUS_BOARD_86 = 0x00023, CBUS_BOARD_86_0188H = 0x00023, CBUS_BOARD_86_0288H = 0x10023, // (未テスト) SIS アミューズメントサウンドボードASB-01 : YM2608 // アドレスデコーダ: 不明 // 0xA460 SOUNDID : 無し // IOADDR: 0088h/008Ah/008Ch/008Eh or 0188h/018Ah/018Ch/018Eh or 0288h/028Ah/028Ch/028Eh or 0388h/038Ah/038Ch/038Eh CBUS_BOARD_ASB01 = 0x00043, CBUS_BOARD_ASB01_0188H = 0x00043, CBUS_BOARD_ASB01_0088H = 0x10043, CBUS_BOARD_ASB01_0288H = 0x20043, CBUS_BOARD_ASB01_0388H = 0x30043, // (未テスト) アイドルジャパン SpeakBoard : YM2608(w/ADPCM-RAM) // アドレスデコーダ: 12bit // IOADDR=0188h/018Ah/018Ch/018Eh or 0088h/008Ah/008Ch/008Eh CBUS_BOARD_SPEAKBOARD = 0x00053, CBUS_BOARD_SPEAKBOARD_188H = 0x00053, CBUS_BOARD_SPEAKBOARD_088H = 0x10053, // (未テスト) コンピュータテクニカ SPB-98 : YM2608, YMF278 CBUS_BOARD_SOUNDPLAYER98 = 0x00063, // (未対応) second-bus86 : YM2608, YMF278B-S CBUS_BOARD_SECONDBUS86 = 0x00073, // (未対応) sound-edge : YAMAHA Sound Edge SW20-98 : YM2608B, YMF278B CBUS_BOARD_SOUNDEDGE = 0x00083, // (未対応) win-duo : YM2608 CBUS_BOARD_WINDUO = 0x00093, // (未テスト) MAD FACTORY 音美 : YM2608(w/ADPCM-RAM), YM3438 // アドレスデコーダ: 不明 // SOUNDID(0xA460) : 無し // IOADDR: 0088h/008Ah/008Ch/008Eh or 0188h/018Ah/018Ch/018Eh or 0288h/028Ah/028Ch/028Eh or 0388h/038Ah/038Ch/038Eh // 0488h/048Ah/048Ch/048Eh or 0588h/058Ah/058Ch/058Eh or 0688h/068Ah/068Ch/068Eh or 0788h/078Ah/078Ch/078Eh CBUS_BOARD_OTOMI = 0x000a3, CBUS_BOARD_OTOMI_188H = 0x000a3, CBUS_BOARD_OTOMI_088H = 0x100a3, CBUS_BOARD_OTOMI_288H = 0x200a3, CBUS_BOARD_OTOMI_388H = 0x300a3, // Q-Vision WaveMaster(86互換) : YM2608, CS4231 // アドレスデコーダ: 16bit以上 // SOUNDID(0xA460) : 0x40 / 0x50 (86互換) // IOADDR: 0188h/018Ah/018Ch/018Eh or 0288h/028Ah/028Ch/028Eh CBUS_BOARD_WAVEMASTER = 0x000b3, CBUS_BOARD_WAVEMASTER_0188H = 0x000b3, CBUS_BOARD_WAVEMASTER_0288H = 0x100b3, // Q-Vision WaveSMIT(86互換) : YMF288-S, CS4231 // アドレスデコーダ: 16bit以上 // SOUNDID(0xA460) : 0x40 / 0x50 (86互換) // IOADDR: 0188h/018Ah/018Ch/018Eh or 0288h/028Ah/028Ch/028Eh CBUS_BOARD_WAVESMIT = 0x000c3, CBUS_BOARD_WAVESMIT_0188H = 0x000c3, CBUS_BOARD_WAVESMIT_0288H = 0x100c3, // Q-Vision WaveStar(86互換) : YMF288-S, CS4231 // アドレスデコーダ: 16bit以上 // SOUNDID(0xA460) : 0x40 / 0x50 (86互換) // IOADDR: 0188h/018Ah/018Ch/018Eh or 0288h/028Ah/028Ch/028Eh CBUS_BOARD_WAVESTAR = 0x000d3, CBUS_BOARD_WAVESTAR_0188H = 0x000d3, CBUS_BOARD_WAVESTAR_0288H = 0x100d3, // (未対応) Buffalo WSN-A4F/A2F : YMF288-S // アドレスデコーダ: 16bit以上 // SOUNDID : 無し // IOADDR : 0188h/018Ah/018Ch/018Eh CBUS_BOARD_WSN_A4F = 0x000e3, // (未対応) SXM-F : YMF288-M // アドレスデコーダ: 不明 // SOUNDID : 無し // IOADDR : 0188h/018Ah/018Ch/018Eh CBUS_BOARD_SXM_F = 0x000f3, // (未対応) SRN-F : YMF288-M // アドレスデコーダ: 16bit以上 // SOUNDID : 無し // IOADDR : 0188h/018Ah/018Ch/018Eh CBUS_BOARD_SRN_F = 0x00103, // YM262(OPL3)系 ================================================= // (未対応) sound-blaster 16 (CT2720) : YMF262-F // アドレスデコーダ : 不明(16bit以下) // IOADDR(OPL3&PCM?): 20Dxh~? (x=2/4/6/8/A/C/E) CBUS_BOARD_SB16 = 0x00004, // (未対応) sound-blaster 16 with YM2203 (CT2720) : YMF262-F, YM2203 // アドレスデコーダ : 不明(16bit以下) // IOADDR(OPN) : 0088h/008Ah or 0188h/018Ah // IOADDR(OPL3&PCM?): 20Dxh~? (x=2/4/6/8/A/C/E) CBUS_BOARD_SB16_2203 = 0x00014, CBUS_BOARD_SB16_2203_0188H = 0x00014, CBUS_BOARD_SB16_2203_0088H = 0x10014, // (未対応) sound-blaster 16Value (CT3720) : YMF262-F // アドレスデコーダ : 不明(16bit以下) CBUS_BOARD_SB16VALUE = 0x00024, // (未対応) canopus PowerWindow T64S : YMF262-M CBUS_BOARD_POWERWINDOW_T64S = 0x00034, // (未対応) EPSON PCSB2 : YMF262-M CBUS_BOARD_PCSB2 = 0x00044, // (未対応) コンピュータテクニカ : YMF262-M CBUS_BOARD_WGS98S = 0x00054, // (未対応) buffalo SRB-G : YMF264-F, YMZ263B-F CBUS_BOARD_SRB_G = 0x00064, // (未対応) SNE MIDI ORCHESTRA MIDI-3 : YM264F CBUS_BOARD_MIDI_ORCHESTRA_MIDI3 = 0x00074, // YM289系 ================================================= // (未対応) SoundBlaster AWE32 (CT3610) : YMF289B-S CBUS_BOARD_SB_AWE32 = 0x00005, // YM297系 ================================================= // NEC PC-9801-118 : YMF297-F // アドレスデコーダ: 不明(16~20bit) // SOUNDID(0xA460) : 0x80 // IOADDR: 0188h/018Ah/018Ch/018Eh CBUS_BOARD_118 = 0x00006 } CBUS_BOARD_TYPE; #endif BambooTracker-0.6.5/BambooTracker/chip/chip.cpp000066400000000000000000000060711476276175200213470ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "chip.hpp" #include #include "resampler.hpp" #include "register_write_logger.hpp" namespace chip { Chip::Chip(int id, int clock, int rate, int autoRate, size_t maxDuration, std::unique_ptr resampler1, std::unique_ptr resampler2, std::shared_ptr logger) : id_(id), rate_(rate), // Dummy set clock_(clock), autoRate_(autoRate), maxDuration_(maxDuration), masterVolumeRatio_(100), logger_(logger) { resampler_[0] = std::move(resampler1); resampler_[1] = std::move(resampler2); for (int pan = STEREO_LEFT; pan <= STEREO_RIGHT; ++pan) { for (auto& buf : buffer_) { buf[pan] = new sample[CHIP_SMPL_BUF_SIZE_]; } } } Chip::~Chip() { for (int pan = STEREO_LEFT; pan <= STEREO_RIGHT; ++pan) { for (auto& buf : buffer_) { delete[] buf[pan]; } } } void Chip::initResampler() { for (int snd = 0; snd < 2; ++snd) { resampler_[snd]->init(internalRate_[snd], rate_, maxDuration_); } } void Chip::reset() { std::lock_guard lg(mutex_); for (int snd = 0; snd < 2; ++snd) { resampler_[snd]->reset(); } resetSpecific(); } void Chip::setRate(int rate) { std::lock_guard lg(mutex_); funcSetRate(rate); for (auto& rsmp : resampler_) { rsmp->setDestributionRate(rate); } } void Chip::funcSetRate(int rate) noexcept { rate_ = rate ? rate : autoRate_; } void Chip::updateVolumeRatio() { for (int i = 0; i < 2; ++i) { updateVolumeRatio(i); } } void Chip::updateVolumeRatio(int i) { volumeRatio_[i] = busVolumeRatio_[i] * masterVolumeRatio_; } void Chip::setMaxDuration(size_t maxDuration) { maxDuration_ = maxDuration; for (int snd = 0; snd < 2; ++snd) { resampler_[snd]->setMaxDuration(maxDuration); } } void Chip::setRegisterWriteLogger(std::shared_ptr logger) { logger_ = logger; } void Chip::setMasterVolume(int percentage) { masterVolumeRatio_ = percentage / 100.0; updateVolumeRatio(); } } BambooTracker-0.6.5/BambooTracker/chip/chip.hpp000066400000000000000000000052131476276175200213510ustar00rootroot00000000000000/* * Copyright (C) 2018-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "chip_defs.h" #include #include #include namespace chip { class AbstractResampler; class AbstractRegisterWriteLogger; class Chip { public: // [rate] // 0 = auto-set mode (set internal chip rate) Chip(int id, int clock, int rate, int autoRate, size_t maxDuration, std::unique_ptr resampler1, std::unique_ptr resampler2, std::shared_ptr logger); virtual ~Chip(); void reset(); virtual void setRegister(uint32_t offset, uint8_t value) = 0; virtual uint8_t getRegister(uint32_t offset) const = 0; virtual void setRate(int rate); int getRate() const noexcept { return rate_; } int getClock() const noexcept { return clock_; } void setMaxDuration(size_t maxDuration); size_t getMaxDuration() const noexcept { return maxDuration_; } void setRegisterWriteLogger(std::shared_ptr logger = nullptr); void setMasterVolume(int percentage); virtual bool mix(int16_t* stream, size_t nSamples) = 0; protected: const int id_; std::mutex mutex_; int rate_, clock_; const int autoRate_; int internalRate_[2]; size_t maxDuration_; double masterVolumeRatio_; double busVolumeRatio_[2]; double volumeRatio_[2]; sample* buffer_[2][2]; std::unique_ptr resampler_[2]; std::shared_ptr logger_; void initResampler(); virtual void resetSpecific() = 0; void funcSetRate(int rate) noexcept; void updateVolumeRatio(); void updateVolumeRatio(int i); }; } BambooTracker-0.6.5/BambooTracker/chip/chip_defs.h000066400000000000000000000023661476276175200220200ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include typedef int32_t sample; enum { CHIP_SMPL_BUF_SIZE_ = 0x10000 }; enum Stereo { STEREO_LEFT, STEREO_RIGHT }; BambooTracker-0.6.5/BambooTracker/chip/codec/000077500000000000000000000000001476276175200207715ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/chip/codec/ymb_codec.hpp000066400000000000000000000035741476276175200234370ustar00rootroot00000000000000/* Encode and decode algorithms for Y8950/YM2608/YM2610 ADPCM-B 2019 by superctr. */ #pragma once #include #include #include #include #include #define CD_CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x))) #define CD_CLAMP_ZERO(x, high) (((x) > (high)) ? (high) : (x)) namespace codec { inline int16_t ymb_step(uint8_t step, int16_t* history, int16_t* step_size) { static const int step_table[8] = { 57, 57, 57, 57, 77, 102, 128, 153 }; int sign = step & 8; int delta = step & 7; int diff = ((1+(delta<<1)) * *step_size) >> 3; int newval = *history; int nstep = (step_table[delta] * *step_size) >> 6; if (sign > 0) newval -= diff; else newval += diff; //*step_size = CLAMP(nstep, 511, 32767); *step_size = CD_CLAMP(nstep, 127, 24576); *history = newval = CD_CLAMP(newval, -32768, 32767); return newval; } inline void ymb_encode(int16_t *buffer,uint8_t *outbuffer,long len) { long i; int16_t step_size = 127; int16_t history = 0; uint8_t buf_sample = 0, nibble = 0; unsigned int adpcm_sample; for(i=0;i>= 4; if(nibble) buffer++; nibble^=4; *outbuffer++ = ymb_step(step, &history, &step_size); } } } #undef CD_CLAMP #undef CD_CLAMP_ZERO BambooTracker-0.6.5/BambooTracker/chip/mame/000077500000000000000000000000001476276175200206335ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/chip/mame/fmopn.c000066400000000000000000004000011476276175200221110ustar00rootroot00000000000000// license:GPL-2.0+ // copyright-holders:Jarek Burczynski,Tatsuyuki Satoh #define YM2610B_WARNING /* ** ** File: fm.c -- software implementation of Yamaha FM sound generator ** ** Copyright Jarek Burczynski (bujar at mame dot net) ** Copyright Tatsuyuki Satoh , MultiArcadeMachineEmulator development ** ** Version 1.5.1 (Genesis Plus GX ym2612.c rev. 368) ** */ /* ** History: ** ** 2006~2012 Eke-Eke (Genesis Plus GX): ** Huge thanks to Nemesis, lot of those fixes came from his tests on Sega Genesis hardware ** More informations at http://gendev.spritesmind.net/forum/viewtopic.php?t=386 ** ** TODO: ** ** - core documentation ** - BUSY flag support ** ** CHANGELOG: ** ** 07-30-2014 dink (FB Alpha project): ** - fixed missing dac channel on savestate load ** ** xx-xx-xxxx ** - fixed LFO implementation: ** .added support for CH3 special mode: fixes various sound effects (birds in Warlock, bug sound in Aladdin...) ** .inverted LFO AM waveform: fixes Spider-Man & Venom : Separation Anxiety (intro), California Games (surfing event) ** .improved LFO timing accuracy: now updated AFTER sample output, like EG/PG updates, and without any precision loss anymore. ** - improved internal timers emulation ** - adjusted lowest EG rates increment values ** - fixed Attack Rate not being updated in some specific cases (Batman & Robin intro) ** - fixed EG behavior when Attack Rate is maximal ** - fixed EG behavior when SL=0 (Mega Turrican tracks 03,09...) or/and Key ON occurs at minimal attenuation ** - implemented EG output immediate changes on register writes ** - fixed YM2612 initial values (after the reset): fixes missing intro in B.O.B ** - implemented Detune overflow (Ariel, Comix Zone, Shaq Fu, Spiderman & many other games using GEMS sound engine) ** - implemented accurate CSM mode emulation ** - implemented accurate SSG-EG emulation (Asterix, Beavis&Butthead, Bubba'n Stix & many other games) ** - implemented accurate address/data ports behavior ** ** 06-23-2007 Zsolt Vasvari: ** - changed the timing not to require the use of floating point calculations ** ** 03-08-2003 Jarek Burczynski: ** - fixed YM2608 initial values (after the reset) ** - fixed flag and irqmask handling (YM2608) ** - fixed BUFRDY flag handling (YM2608) ** ** 14-06-2003 Jarek Burczynski: ** - implemented all of the YM2608 status register flags ** - implemented support for external memory read/write via YM2608 ** - implemented support for deltat memory limit register in YM2608 emulation ** ** 22-05-2003 Jarek Burczynski: ** - fixed LFO PM calculations (copy&paste bugfix) ** ** 08-05-2003 Jarek Burczynski: ** - fixed SSG support ** ** 22-04-2003 Jarek Burczynski: ** - implemented 100% correct LFO generator (verified on real YM2610 and YM2608) ** ** 15-04-2003 Jarek Burczynski: ** - added support for YM2608's register 0x110 - status mask ** ** 01-12-2002 Jarek Burczynski: ** - fixed register addressing in YM2608, YM2610, YM2610B chips. (verified on real YM2608) ** The addressing patch used for early Neo-Geo games can be removed now. ** ** 26-11-2002 Jarek Burczynski, Nicola Salmoria: ** - recreated YM2608 ADPCM ROM using data from real YM2608's output which leads to: ** - added emulation of YM2608 drums. ** - output of YM2608 is two times lower now - same as YM2610 (verified on real YM2608) ** ** 16-08-2002 Jarek Burczynski: ** - binary exact Envelope Generator (verified on real YM2203); ** identical to YM2151 ** - corrected 'off by one' error in feedback calculations (when feedback is off) ** - corrected connection (algorithm) calculation (verified on real YM2203 and YM2610) ** ** 18-12-2001 Jarek Burczynski: ** - added SSG-EG support (verified on real YM2203) ** ** 12-08-2001 Jarek Burczynski: ** - corrected sin_tab and tl_tab data (verified on real chip) ** - corrected feedback calculations (verified on real chip) ** - corrected phase generator calculations (verified on real chip) ** - corrected envelope generator calculations (verified on real chip) ** - corrected FM volume level (YM2610 and YM2610B). ** - changed YMxxxUpdateOne() functions (YM2203, YM2608, YM2610, YM2610B, YM2612) : ** this was needed to calculate YM2610 FM channels output correctly. ** (Each FM channel is calculated as in other chips, but the output of the channel ** gets shifted right by one *before* sending to accumulator. That was impossible to do ** with previous implementation). ** ** 23-07-2001 Jarek Burczynski, Nicola Salmoria: ** - corrected YM2610 ADPCM type A algorithm and tables (verified on real chip) ** ** 11-06-2001 Jarek Burczynski: ** - corrected end of sample bug in ADPCMA_calc_cha(). ** Real YM2610 checks for equality between current and end addresses (only 20 LSB bits). ** ** 08-12-98 hiro-shi: ** rename ADPCMA -> ADPCMB, ADPCMB -> ADPCMA ** move ROM limit check.(CALC_CH? -> 2610Write1/2) ** test program (ADPCMB_TEST) ** move ADPCM A/B end check. ** ADPCMB repeat flag(no check) ** change ADPCM volume rate (8->16) (32->48). ** ** 09-12-98 hiro-shi: ** change ADPCM volume. (8->16, 48->64) ** replace ym2610 ch0/3 (YM-2610B) ** change ADPCM_SHIFT (10->8) missing bank change 0x4000-0xffff. ** add ADPCM_SHIFT_MASK ** change ADPCMA_DECODE_MIN/MAX. */ /************************************************************************/ /* comment of hiro-shi(Hiromitsu Shioya) */ /* YM2610(B) = OPN-B */ /* YM2610 : PSG:3ch FM:4ch ADPCM(18.5KHz):6ch DeltaT ADPCM:1ch */ /* YM2610B : PSG:3ch FM:6ch ADPCM(18.5KHz):6ch DeltaT ADPCM:1ch */ /************************************************************************/ #include #include // for memset #include // for NULL #define _USE_MATH_DEFINES #include #include "fmopn.h" /* include external DELTA-T unit (when needed) */ #if (BUILD_YM2608||BUILD_YM2610||BUILD_YM2610B) #include "ymdeltat.h" #endif /* shared function building option */ #define BUILD_OPN (BUILD_YM2203||BUILD_YM2608||BUILD_YM2610||BUILD_YM2610B||BUILD_YM2612) #define BUILD_OPN_PRESCALER (BUILD_YM2203||BUILD_YM2608) /* globals */ #define TYPE_SSG 0x01 /* SSG support */ #define TYPE_LFOPAN 0x02 /* OPN type LFO and PAN */ #define TYPE_6CH 0x04 /* FM 6CH / 3CH */ #define TYPE_DAC 0x08 /* YM2612's DAC device */ #define TYPE_ADPCM 0x10 /* two ADPCM units */ #define TYPE_2610 0x20 /* bogus flag to differentiate 2608 from 2610 */ #define TYPE_YM2203 (TYPE_SSG) #define TYPE_YM2608 (TYPE_SSG |TYPE_LFOPAN |TYPE_6CH |TYPE_ADPCM) #define TYPE_YM2610 (TYPE_SSG |TYPE_LFOPAN |TYPE_6CH |TYPE_ADPCM |TYPE_2610) #define TYPE_YM2612 (TYPE_DAC |TYPE_LFOPAN |TYPE_6CH) /* globals */ #define FREQ_SH 16 /* 16.16 fixed point (frequency calculations) */ #define EG_SH 16 /* 16.16 fixed point (envelope generator timing) */ #define LFO_SH 24 /* 8.24 fixed point (LFO calculations) */ #define TIMER_SH 16 /* 16.16 fixed point (timers calculations) */ #define FREQ_MASK ((1<>3) /* sin waveform table in 'decibel' scale */ static unsigned int sin_tab[SIN_LEN]; /* sustain level table (3dB per step) */ /* bit0, bit1, bit2, bit3, bit4, bit5, bit6 */ /* 1, 2, 4, 8, 16, 32, 64 (value)*/ /* 0.75, 1.5, 3, 6, 12, 24, 48 (dB)*/ /* 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB)*/ /* attenuation value (10 bits) = (SL << 2) << 3 */ #define SC(db) (UINT32) ( db * (4.0/ENV_STEP) ) static const UINT32 sl_table[16]={ SC( 0),SC( 1),SC( 2),SC(3 ),SC(4 ),SC(5 ),SC(6 ),SC( 7), SC( 8),SC( 9),SC(10),SC(11),SC(12),SC(13),SC(14),SC(31) }; #undef SC #define RATE_STEPS (8) static const UINT8 eg_inc[19*RATE_STEPS]={ /*cycle:0 1 2 3 4 5 6 7*/ /* 0 */ 0,1, 0,1, 0,1, 0,1, /* rates 00..11 0 (increment by 0 or 1) */ /* 1 */ 0,1, 0,1, 1,1, 0,1, /* rates 00..11 1 */ /* 2 */ 0,1, 1,1, 0,1, 1,1, /* rates 00..11 2 */ /* 3 */ 0,1, 1,1, 1,1, 1,1, /* rates 00..11 3 */ /* 4 */ 1,1, 1,1, 1,1, 1,1, /* rate 12 0 (increment by 1) */ /* 5 */ 1,1, 1,2, 1,1, 1,2, /* rate 12 1 */ /* 6 */ 1,2, 1,2, 1,2, 1,2, /* rate 12 2 */ /* 7 */ 1,2, 2,2, 1,2, 2,2, /* rate 12 3 */ /* 8 */ 2,2, 2,2, 2,2, 2,2, /* rate 13 0 (increment by 2) */ /* 9 */ 2,2, 2,4, 2,2, 2,4, /* rate 13 1 */ /*10 */ 2,4, 2,4, 2,4, 2,4, /* rate 13 2 */ /*11 */ 2,4, 4,4, 2,4, 4,4, /* rate 13 3 */ /*12 */ 4,4, 4,4, 4,4, 4,4, /* rate 14 0 (increment by 4) */ /*13 */ 4,4, 4,8, 4,4, 4,8, /* rate 14 1 */ /*14 */ 4,8, 4,8, 4,8, 4,8, /* rate 14 2 */ /*15 */ 4,8, 8,8, 4,8, 8,8, /* rate 14 3 */ /*16 */ 8,8, 8,8, 8,8, 8,8, /* rates 15 0, 15 1, 15 2, 15 3 (increment by 8) */ /*17 */ 16,16,16,16,16,16,16,16, /* rates 15 2, 15 3 for attack */ /*18 */ 0,0, 0,0, 0,0, 0,0, /* infinity rates for attack and decay(s) */ }; #define O(a) (a*RATE_STEPS) /*note that there is no O(17) in this table - it's directly in the code */ static const UINT8 eg_rate_select[32+64+32]={ /* Envelope Generator rates (32 + 64 rates + 32 RKS) */ /* 32 infinite time rates (same as Rate 0) */ O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), /* rates 00-11 */ /* O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), */ O(18),O(18),O( 0),O( 0), O( 0),O( 0),O( 2),O( 2), // Nemesis's tests O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), /* rate 12 */ O( 4),O( 5),O( 6),O( 7), /* rate 13 */ O( 8),O( 9),O(10),O(11), /* rate 14 */ O(12),O(13),O(14),O(15), /* rate 15 */ O(16),O(16),O(16),O(16), /* 32 dummy rates (same as 15 3) */ O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16), O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16), O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16), O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16) }; #undef O /*rate 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15*/ /*shift 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0 */ /*mask 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0, 0, 0, 0, 0 */ #define O(a) (a*1) static const UINT8 eg_rate_shift[32+64+32]={ /* Envelope Generator counter shifts (32 + 64 rates + 32 RKS) */ /* 32 infinite time rates */ /* O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), */ /* fixed (should be the same as rate 0, even if it makes no difference since increment value is 0 for these rates) */ O(11),O(11),O(11),O(11),O(11),O(11),O(11),O(11), O(11),O(11),O(11),O(11),O(11),O(11),O(11),O(11), O(11),O(11),O(11),O(11),O(11),O(11),O(11),O(11), O(11),O(11),O(11),O(11),O(11),O(11),O(11),O(11), /* rates 00-11 */ O(11),O(11),O(11),O(11), O(10),O(10),O(10),O(10), O( 9),O( 9),O( 9),O( 9), O( 8),O( 8),O( 8),O( 8), O( 7),O( 7),O( 7),O( 7), O( 6),O( 6),O( 6),O( 6), O( 5),O( 5),O( 5),O( 5), O( 4),O( 4),O( 4),O( 4), O( 3),O( 3),O( 3),O( 3), O( 2),O( 2),O( 2),O( 2), O( 1),O( 1),O( 1),O( 1), O( 0),O( 0),O( 0),O( 0), /* rate 12 */ O( 0),O( 0),O( 0),O( 0), /* rate 13 */ O( 0),O( 0),O( 0),O( 0), /* rate 14 */ O( 0),O( 0),O( 0),O( 0), /* rate 15 */ O( 0),O( 0),O( 0),O( 0), /* 32 dummy rates (same as 15 3) */ O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0) }; #undef O static const UINT8 dt_tab[4 * 32]={ /* this is YM2151 and YM2612 phase increment data (in 10.10 fixed point format)*/ /* FD=0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* FD=1 */ 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 8, 8, 8, /* FD=2 */ 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 8, 9,10,11,12,13,14,16,16,16,16, /* FD=3 */ 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8 , 8, 9,10,11,12,13,14,16,17,19,20,22,22,22,22 }; /* OPN key frequency number -> key code follow table */ /* fnum higher 4bit -> keycode lower 2bit */ static const UINT8 opn_fktable[16] = {0,0,0,0,0,0,0,1,2,3,3,3,3,3,3,3}; /* 8 LFO speed parameters */ /* each value represents number of samples that one LFO level will last for */ static const UINT32 lfo_samples_per_step[8] = {108, 77, 71, 67, 62, 44, 8, 5}; /*There are 4 different LFO AM depths available, they are: 0 dB, 1.4 dB, 5.9 dB, 11.8 dB Here is how it is generated (in EG steps): 11.8 dB = 0, 2, 4, 6, 8, 10,12,14,16...126,126,124,122,120,118,....4,2,0 5.9 dB = 0, 1, 2, 3, 4, 5, 6, 7, 8....63, 63, 62, 61, 60, 59,.....2,1,0 1.4 dB = 0, 0, 0, 0, 1, 1, 1, 1, 2,...15, 15, 15, 15, 14, 14,.....0,0,0 (1.4 dB is losing precision as you can see) It's implemented as generator from 0..126 with step 2 then a shift right N times, where N is: 8 for 0 dB 3 for 1.4 dB 1 for 5.9 dB 0 for 11.8 dB */ static const UINT8 lfo_ams_depth_shift[4] = {8, 3, 1, 0}; /*There are 8 different LFO PM depths available, they are: 0, 3.4, 6.7, 10, 14, 20, 40, 80 (cents) Modulation level at each depth depends on F-NUMBER bits: 4,5,6,7,8,9,10 (bits 8,9,10 = FNUM MSB from OCT/FNUM register) Here we store only first quarter (positive one) of full waveform. Full table (lfo_pm_table) containing all 128 waveforms is build at run (init) time. One value in table below represents 4 (four) basic LFO steps (1 PM step = 4 AM steps). For example: at LFO SPEED=0 (which is 108 samples per basic LFO step) one value from "lfo_pm_output" table lasts for 432 consecutive samples (4*108=432) and one full LFO waveform cycle lasts for 13824 samples (32*432=13824; 32 because we store only a quarter of whole waveform in the table below) */ static const UINT8 lfo_pm_output[7*8][8]={ /* 7 bits meaningful (of F-NUMBER), 8 LFO output levels per one depth (out of 32), 8 LFO depths */ /* FNUM BIT 4: 000 0001xxxx */ /* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 3 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 4 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 5 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 6 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 7 */ {0, 0, 0, 0, 1, 1, 1, 1}, /* FNUM BIT 5: 000 0010xxxx */ /* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 3 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 4 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 5 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 6 */ {0, 0, 0, 0, 1, 1, 1, 1}, /* DEPTH 7 */ {0, 0, 1, 1, 2, 2, 2, 3}, /* FNUM BIT 6: 000 0100xxxx */ /* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 3 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 4 */ {0, 0, 0, 0, 0, 0, 0, 1}, /* DEPTH 5 */ {0, 0, 0, 0, 1, 1, 1, 1}, /* DEPTH 6 */ {0, 0, 1, 1, 2, 2, 2, 3}, /* DEPTH 7 */ {0, 0, 2, 3, 4, 4, 5, 6}, /* FNUM BIT 7: 000 1000xxxx */ /* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 1, 1}, /* DEPTH 3 */ {0, 0, 0, 0, 1, 1, 1, 1}, /* DEPTH 4 */ {0, 0, 0, 1, 1, 1, 1, 2}, /* DEPTH 5 */ {0, 0, 1, 1, 2, 2, 2, 3}, /* DEPTH 6 */ {0, 0, 2, 3, 4, 4, 5, 6}, /* DEPTH 7 */ {0, 0, 4, 6, 8, 8, 0xa, 0xc}, /* FNUM BIT 8: 001 0000xxxx */ /* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 1 */ {0, 0, 0, 0, 1, 1, 1, 1}, /* DEPTH 2 */ {0, 0, 0, 1, 1, 1, 2, 2}, /* DEPTH 3 */ {0, 0, 1, 1, 2, 2, 3, 3}, /* DEPTH 4 */ {0, 0, 1, 2, 2, 2, 3, 4}, /* DEPTH 5 */ {0, 0, 2, 3, 4, 4, 5, 6}, /* DEPTH 6 */ {0, 0, 4, 6, 8, 8, 0xa, 0xc}, /* DEPTH 7 */ {0, 0, 8, 0xc,0x10,0x10,0x14,0x18}, /* FNUM BIT 9: 010 0000xxxx */ /* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 1 */ {0, 0, 0, 0, 2, 2, 2, 2}, /* DEPTH 2 */ {0, 0, 0, 2, 2, 2, 4, 4}, /* DEPTH 3 */ {0, 0, 2, 2, 4, 4, 6, 6}, /* DEPTH 4 */ {0, 0, 2, 4, 4, 4, 6, 8}, /* DEPTH 5 */ {0, 0, 4, 6, 8, 8, 0xa, 0xc}, /* DEPTH 6 */ {0, 0, 8, 0xc,0x10,0x10,0x14,0x18}, /* DEPTH 7 */ {0, 0,0x10,0x18,0x20,0x20,0x28,0x30}, /* FNUM BIT10: 100 0000xxxx */ /* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 1 */ {0, 0, 0, 0, 4, 4, 4, 4}, /* DEPTH 2 */ {0, 0, 0, 4, 4, 4, 8, 8}, /* DEPTH 3 */ {0, 0, 4, 4, 8, 8, 0xc, 0xc}, /* DEPTH 4 */ {0, 0, 4, 8, 8, 8, 0xc,0x10}, /* DEPTH 5 */ {0, 0, 8, 0xc,0x10,0x10,0x14,0x18}, /* DEPTH 6 */ {0, 0,0x10,0x18,0x20,0x20,0x28,0x30}, /* DEPTH 7 */ {0, 0,0x20,0x30,0x40,0x40,0x50,0x60}, }; /* all 128 LFO PM waveforms */ static INT32 lfo_pm_table[128*8*32]; /* 128 combinations of 7 bits meaningful (of F-NUMBER), 8 LFO depths, 32 LFO output levels per one depth */ /* register number to channel number , slot offset */ #define OPN_CHAN(N) (N&3) #define OPN_SLOT(N) ((N>>2)&3) /* slot number */ #define SLOT1 0 #define SLOT2 2 #define SLOT3 1 #define SLOT4 3 /* bit0 = Right enable , bit1 = Left enable */ #define OUTD_RIGHT 1 #define OUTD_LEFT 2 #define OUTD_CENTER 3 /* struct describing a single operator (SLOT) */ typedef struct { INT32 *DT; /* detune :dt_tab[DT] */ UINT8 KSR; /* key scale rate :3-KSR */ UINT32 ar; /* attack rate */ UINT32 d1r; /* decay rate */ UINT32 d2r; /* sustain rate */ UINT32 rr; /* release rate */ UINT8 ksr; /* key scale rate :kcode>>(3-KSR) */ UINT32 mul; /* multiple :ML_TABLE[ML] */ /* Phase Generator */ UINT32 phase; /* phase counter */ INT32 Incr; /* phase step */ /* Envelope Generator */ UINT8 state; /* phase type */ UINT32 tl; /* total level: TL << 3 */ INT32 volume; /* envelope counter */ UINT32 sl; /* sustain level:sl_table[SL] */ UINT32 vol_out; /* current output from EG circuit (without AM from LFO) */ UINT8 eg_sh_ar; /* (attack state) */ UINT8 eg_sel_ar; /* (attack state) */ UINT8 eg_sh_d1r; /* (decay state) */ UINT8 eg_sel_d1r; /* (decay state) */ UINT8 eg_sh_d2r; /* (sustain state) */ UINT8 eg_sel_d2r; /* (sustain state) */ UINT8 eg_sh_rr; /* (release state) */ UINT8 eg_sel_rr; /* (release state) */ UINT8 ssg; /* SSG-EG waveform */ UINT8 ssgn; /* SSG-EG negated output */ UINT8 key; /* 0=last key was KEY OFF, 1=KEY ON */ /* LFO */ UINT32 AMmask; /* AM enable flag */ } FM_SLOT; typedef struct { FM_SLOT SLOT[4]; /* four SLOTs (operators) */ UINT8 ALGO; /* algorithm */ UINT8 FB; /* feedback shift */ INT32 op1_out[2]; /* op1 output for feedback */ INT32 *connect1; /* SLOT1 output pointer */ INT32 *connect3; /* SLOT3 output pointer */ INT32 *connect2; /* SLOT2 output pointer */ INT32 *connect4; /* SLOT4 output pointer */ INT32 *mem_connect;/* where to put the delayed sample (MEM) */ INT32 mem_value; /* delayed sample (MEM) value */ INT32 pms; /* channel PMS */ UINT8 ams; /* channel AMS */ UINT32 fc; /* fnum,blk:adjusted to sample rate */ UINT8 kcode; /* key code: */ UINT32 block_fnum; /* current blk/fnum value for this slot (can be different betweeen slots of one channel in 3slot mode) */ UINT8 Muted; } FM_CH; typedef struct { void * param; /* this chip parameter */ UINT32 clock; /* master clock (Hz) */ UINT32 rate; /* sampling rate (Hz) */ double freqbase; /* frequency base */ int timer_prescaler; /* timer prescaler */ #if FM_BUSY_FLAG_SUPPORT TIME_TYPE busy_expiry_time; /* expiry time of the busy status */ #endif UINT8 address; /* address register */ UINT8 irq; /* interrupt level */ UINT8 irqmask; /* irq mask */ UINT8 status; /* status flag */ UINT32 mode; /* mode CSM / 3SLOT */ UINT8 fn_h; /* freq latch */ UINT8 prescaler_sel; /* prescaler selector */ INT32 TA; /* timer a */ INT32 TAC; /* timer a counter */ UINT8 TB; /* timer b */ INT32 TBC; /* timer b counter */ /* local time tables */ INT32 dt_tab[8][32]; /* DeTune table */ /* Extention Timer and IRQ handler */ FM_TIMERHANDLER timer_handler; FM_IRQHANDLER IRQ_Handler; ssg_callbacks SSG_funcs; void * SSG_param; } FM_ST; /***********************************************************/ /* OPN unit */ /***********************************************************/ /* OPN 3slot struct */ typedef struct { UINT32 fc[3]; /* fnum3,blk3: calculated */ UINT8 fn_h; /* freq3 latch */ UINT8 kcode[3]; /* key code */ UINT32 block_fnum[3]; /* current fnum value for this slot (can be different betweeen slots of one channel in 3slot mode) */ UINT8 key_csm; /* CSM mode Key-ON flag */ } FM_3SLOT; /* OPN/A/B common state */ typedef struct { UINT8 type; /* chip type */ UINT8 smpRateNative; /* emulating at native sample rate (enable sample rate change callback) */ UINT8 LegacyMode; /* behave like old emulation regarding FNum writes and Key Off */ FM_ST ST; /* general state */ FM_3SLOT SL3; /* 3 slot mode state */ FM_CH *P_CH; /* pointer of CH */ UINT32 pan[6*2]; /* fm channels output masks (0xffffffff = enable) */ UINT32 eg_cnt; /* global envelope generator counter */ UINT32 eg_timer; /* global envelope generator counter works at frequency = chipclock/144/3 */ UINT32 eg_timer_add; /* step of eg_timer */ UINT32 eg_timer_overflow;/* envelope generator timer overflows every 3 samples (on real chip) */ /* there are 2048 FNUMs that can be generated using FNUM/BLK registers but LFO works with one more bit of a precision so we really need 4096 elements */ UINT32 fn_table[4096]; /* fnumber->increment counter */ UINT32 fn_max; /* maximal phase increment (used for phase overflow) */ /* LFO */ UINT8 lfo_cnt; /* current LFO phase (out of 128) */ UINT32 lfo_timer; /* current LFO phase runs at LFO frequency */ UINT32 lfo_timer_add; /* step of lfo_timer */ UINT32 lfo_timer_overflow; /* LFO timer overflows every N samples (depends on LFO frequency) */ UINT32 LFO_AM; /* current LFO AM step */ UINT32 LFO_PM; /* current LFO PM step */ INT32 m2,c1,c2; /* Phase Modulation input for operators 2,3,4 */ INT32 mem; /* one sample delay memory */ INT32 out_fm[6]; /* outputs of working channels */ #if (BUILD_YM2608||BUILD_YM2610||BUILD_YM2610B) INT32 out_adpcm[4]; /* channel output NONE,LEFT,RIGHT or CENTER for YM2608/YM2610 ADPCM */ INT32 out_delta[4]; /* channel output NONE,LEFT,RIGHT or CENTER for YM2608/YM2610 DELTAT*/ #endif DEVCB_SRATE_CHG smpRateFunc; void* smpRateData; } FM_OPN; /* log output level */ #define LOG_ERR 3 /* ERROR */ #define LOG_WAR 2 /* WARNING */ #define LOG_INF 1 /* INFORMATION */ #define LOG_LEVEL LOG_INF #ifndef __RAINE__ #define LOG(n,x) do { if( (n)>=LOG_LEVEL ) logerror x; } while (0) #endif /* limitter */ #define Limit(val, max,min) { \ if ( val > max ) val = max; \ else if ( val < min ) val = min; \ } static UINT8 tablesInit = 0; /* status set and IRQ handling */ INLINE void FM_STATUS_SET(FM_ST *ST,int flag) { /* set status flag */ ST->status |= flag; if ( !(ST->irq) && (ST->status & ST->irqmask) ) { ST->irq = 1; /* callback user interrupt handler (IRQ is OFF to ON) */ if(ST->IRQ_Handler) (ST->IRQ_Handler)(ST->param,1); } } /* status reset and IRQ handling */ INLINE void FM_STATUS_RESET(FM_ST *ST,int flag) { /* reset status flag */ ST->status &=~flag; if ( (ST->irq) && !(ST->status & ST->irqmask) ) { ST->irq = 0; /* callback user interrupt handler (IRQ is ON to OFF) */ if(ST->IRQ_Handler) (ST->IRQ_Handler)(ST->param,0); } } /* IRQ mask set */ INLINE void FM_IRQMASK_SET(FM_ST *ST,int flag) { ST->irqmask = (UINT8)flag; /* IRQ handling check */ FM_STATUS_SET(ST,0); FM_STATUS_RESET(ST,0); } INLINE void FM_KEYON(FM_OPN *OPN, FM_CH *CH , int s ) { FM_SLOT *SLOT = &CH->SLOT[s]; // Note by Valley Bell: // I assume that the CSM mode shouldn't affect channels // other than FM3, so I added a check for it here. if( !SLOT->key && (!OPN->SL3.key_csm || CH == &OPN->P_CH[2])) { /* restart Phase Generator */ SLOT->phase = 0; /* reset SSG-EG inversion flag */ SLOT->ssgn = 0; if ((SLOT->ar + SLOT->ksr) < 94 /*32+62*/) { SLOT->state = (SLOT->volume <= MIN_ATT_INDEX) ? ((SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC) : EG_ATT; } else { /* force attenuation level to 0 */ SLOT->volume = MIN_ATT_INDEX; /* directly switch to Decay (or Sustain) */ SLOT->state = (SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC; } /* recalculate EG output */ if ((SLOT->ssg&0x08) && (SLOT->ssgn ^ (SLOT->ssg&0x04))) SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; else SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; } SLOT->key = 1; } INLINE void FM_KEYOFF(FM_OPN *OPN, FM_CH *CH , int s ) { FM_SLOT *SLOT = &CH->SLOT[s]; if (SLOT->key && (!OPN->SL3.key_csm || CH == &OPN->P_CH[2])) { if (SLOT->state>EG_REL) { SLOT->state = EG_REL; /* phase -> Release */ /* SSG-EG specific update */ if (SLOT->ssg&0x08) { /* convert EG attenuation level */ if (SLOT->ssgn ^ (SLOT->ssg&0x04)) SLOT->volume = (0x200 - SLOT->volume); /* force EG attenuation level */ if (SLOT->volume >= 0x200) { SLOT->volume = MAX_ATT_INDEX; SLOT->state = EG_OFF; } /* recalculate EG output */ SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; } } if (OPN->LegacyMode) // workaround for VGMs trimmed with VGMTool { // When at maximum release rate AND there was a Key On just this sample, enforce an instant Key Off. if (SLOT->rr >= 94 && SLOT->phase == 0) { SLOT->volume = MAX_ATT_INDEX; SLOT->state = EG_OFF; } } } SLOT->key = 0; } INLINE void FM_KEYON_CSM(FM_OPN *OPN, FM_CH *CH , int s ) { FM_SLOT *SLOT = &CH->SLOT[s]; if (!SLOT->key && !OPN->SL3.key_csm) { /* restart Phase Generator */ SLOT->phase = 0; /* reset SSG-EG inversion flag */ SLOT->ssgn = 0; if ((SLOT->ar + SLOT->ksr) < 94 /*32+62*/) { SLOT->state = (SLOT->volume <= MIN_ATT_INDEX) ? ((SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC) : EG_ATT; } else { /* force attenuation level to 0 */ SLOT->volume = MIN_ATT_INDEX; /* directly switch to Decay (or Sustain) */ SLOT->state = (SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC; } /* recalculate EG output */ if ((SLOT->ssg&0x08) && (SLOT->ssgn ^ (SLOT->ssg&0x04))) SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; else SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; } } INLINE void FM_KEYOFF_CSM(FM_CH *CH , int s ) { FM_SLOT *SLOT = &CH->SLOT[s]; if (!SLOT->key) { if (SLOT->state>EG_REL) { SLOT->state = EG_REL; /* phase -> Release */ /* SSG-EG specific update */ if (SLOT->ssg&0x08) { /* convert EG attenuation level */ if (SLOT->ssgn ^ (SLOT->ssg&0x04)) SLOT->volume = (0x200 - SLOT->volume); /* force EG attenuation level */ if (SLOT->volume >= 0x200) { SLOT->volume = MAX_ATT_INDEX; SLOT->state = EG_OFF; } /* recalculate EG output */ SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; } } } } /* OPN Mode Register Write */ INLINE void set_timers( FM_OPN *OPN, FM_ST *ST, void *n, int v ) { /* b7 = CSM MODE */ /* b6 = 3 slot mode */ /* b5 = reset b */ /* b4 = reset a */ /* b3 = timer enable b */ /* b2 = timer enable a */ /* b1 = load b */ /* b0 = load a */ if ((OPN->ST.mode ^ v) & 0xC0) { /* phase increment need to be recalculated */ OPN->P_CH[2].SLOT[SLOT1].Incr=-1; /* CSM mode disabled and CSM key ON active*/ if (((v & 0xC0) != 0x80) && OPN->SL3.key_csm) { /* CSM Mode Key OFF (verified by Nemesis on real hardware) */ FM_KEYOFF_CSM(&OPN->P_CH[2],SLOT1); FM_KEYOFF_CSM(&OPN->P_CH[2],SLOT2); FM_KEYOFF_CSM(&OPN->P_CH[2],SLOT3); FM_KEYOFF_CSM(&OPN->P_CH[2],SLOT4); OPN->SL3.key_csm = 0; } } /* reload Timers */ if ((v&1) && !(ST->mode&1)) { ST->TAC = (1024-ST->TA); /* External timer handler */ if (ST->timer_handler) (ST->timer_handler)(n,0,ST->TAC * ST->timer_prescaler,ST->clock); ST->TAC *= 4096; } else if (!(v & 1)) { if( ST->TAC != 0 ) { ST->TAC = 0; if (ST->timer_handler) (ST->timer_handler)(n,0,0,ST->clock); } } if ((v&2) && !(ST->mode&2)) { ST->TBC = ( 256-ST->TB)<<4; /* External timer handler */ if (ST->timer_handler) (ST->timer_handler)(n,1,ST->TBC * ST->timer_prescaler,ST->clock); ST->TBC *= 4096; } else if (!(v & 2)) { if( ST->TBC != 0 ) { ST->TBC = 0; if (ST->timer_handler) (ST->timer_handler)(n,1,0,ST->clock); } } /* reset Timers flags */ ST->status &= (~v >> 4); /* if IRQ should be lowered now, do so */ if ( (ST->irq) && !(ST->status & ST->irqmask) ) { ST->irq = 0; /* callback user interrupt handler (IRQ is ON to OFF) */ if(ST->IRQ_Handler) (ST->IRQ_Handler)(ST->param, 0); } ST->mode = v; } /* Timer A Overflow */ INLINE void TimerAOver(FM_ST *ST) { /* set status (if enabled) */ if(ST->mode & 0x04) FM_STATUS_SET(ST,0x01); /* clear or reload the counter */ ST->TAC = (1024-ST->TA); if (ST->timer_handler) (ST->timer_handler)(ST->param,0,ST->TAC * ST->timer_prescaler,ST->clock); ST->TAC *= 4096; } /* Timer B Overflow */ INLINE void TimerBOver(FM_ST *ST) { /* set status (if enabled) */ if(ST->mode & 0x08) FM_STATUS_SET(ST,0x02); /* clear or reload the counter */ ST->TBC = ( 256-ST->TB)<<4; if (ST->timer_handler) (ST->timer_handler)(ST->param,1,ST->TBC * ST->timer_prescaler,ST->clock); ST->TBC *= 4096; } #if FM_INTERNAL_TIMER /* ----- internal timer mode , update timer */ /* ---------- calculate timer A ---------- */ #define INTERNAL_TIMER_A(ST,CSM_CH) \ { \ if( (ST)->TAC && ((ST)->timer_handler==0) ) \ if( ((ST)->TAC -= (int)((ST)->freqbase*4096)) <= 0 ) \ { \ TimerAOver( ST ); \ /* CSM mode total level latch and auto key on */ \ if( (ST)->mode & 0x80 ) \ CSMKeyControll( OPN, CSM_CH ); \ } \ } /* ---------- calculate timer B ---------- */ #define INTERNAL_TIMER_B(ST,step) \ { \ if( (ST)->TBC && ((ST)->timer_handler==0) ) \ if( ((ST)->TBC -= (int)((ST)->freqbase*4096*step)) <= 0 ) \ TimerBOver( ST ); \ } #else /* FM_INTERNAL_TIMER */ /* external timer mode */ #define INTERNAL_TIMER_A(ST,CSM_CH) #define INTERNAL_TIMER_B(ST,step) #endif /* FM_INTERNAL_TIMER */ #if FM_BUSY_FLAG_SUPPORT #define FM_BUSY_CLEAR(ST) ((ST)->busy_expiry_time = UNDEFINED_TIME) INLINE UINT8 FM_STATUS_FLAG(FM_ST *ST) { if( COMPARE_TIMES(ST->busy_expiry_time, UNDEFINED_TIME) != 0 ) { if (COMPARE_TIMES(ST->busy_expiry_time, FM_GET_TIME_NOW(&ST->device->machine())) > 0) return ST->status | 0x80; /* with busy */ /* expire */ FM_BUSY_CLEAR(ST); } return ST->status; } INLINE void FM_BUSY_SET(FM_ST *ST,int busyclock ) { TIME_TYPE expiry_period = MULTIPLY_TIME_BY_INT(attotime::from_hz(ST->clock), busyclock * ST->timer_prescaler); ST->busy_expiry_time = ADD_TIMES(FM_GET_TIME_NOW(&ST->device->machine()), expiry_period); } #else #define FM_STATUS_FLAG(ST) ((ST)->status) #define FM_BUSY_SET(ST,bclock) {} #define FM_BUSY_CLEAR(ST) {} #endif /* set algorithm connection */ INLINE void setup_connection( FM_OPN *OPN, FM_CH *CH, int ch ) { INT32 *carrier = &OPN->out_fm[ch]; INT32 **om1 = &CH->connect1; INT32 **om2 = &CH->connect3; INT32 **oc1 = &CH->connect2; INT32 **memc = &CH->mem_connect; switch( CH->ALGO ) { case 0: /* M1---C1---MEM---M2---C2---OUT */ *om1 = &OPN->c1; *oc1 = &OPN->mem; *om2 = &OPN->c2; *memc= &OPN->m2; break; case 1: /* M1------+-MEM---M2---C2---OUT */ /* C1-+ */ *om1 = &OPN->mem; *oc1 = &OPN->mem; *om2 = &OPN->c2; *memc= &OPN->m2; break; case 2: /* M1-----------------+-C2---OUT */ /* C1---MEM---M2-+ */ *om1 = &OPN->c2; *oc1 = &OPN->mem; *om2 = &OPN->c2; *memc= &OPN->m2; break; case 3: /* M1---C1---MEM------+-C2---OUT */ /* M2-+ */ *om1 = &OPN->c1; *oc1 = &OPN->mem; *om2 = &OPN->c2; *memc= &OPN->c2; break; case 4: /* M1---C1-+-OUT */ /* M2---C2-+ */ /* MEM: not used */ *om1 = &OPN->c1; *oc1 = carrier; *om2 = &OPN->c2; *memc= &OPN->mem; /* store it anywhere where it will not be used */ break; case 5: /* +----C1----+ */ /* M1-+-MEM---M2-+-OUT */ /* +----C2----+ */ *om1 = NULL; /* special mark */ *oc1 = carrier; *om2 = carrier; *memc= &OPN->m2; break; case 6: /* M1---C1-+ */ /* M2-+-OUT */ /* C2-+ */ /* MEM: not used */ *om1 = &OPN->c1; *oc1 = carrier; *om2 = carrier; *memc= &OPN->mem; /* store it anywhere where it will not be used */ break; case 7: /* M1-+ */ /* C1-+-OUT */ /* M2-+ */ /* C2-+ */ /* MEM: not used*/ *om1 = carrier; *oc1 = carrier; *om2 = carrier; *memc= &OPN->mem; /* store it anywhere where it will not be used */ break; } CH->connect4 = carrier; } /* set detune & multiple */ INLINE void set_det_mul(FM_ST *ST,FM_CH *CH,FM_SLOT *SLOT,int v) { SLOT->mul = (v&0x0f)? (v&0x0f)*2 : 1; SLOT->DT = ST->dt_tab[(v>>4)&7]; CH->SLOT[SLOT1].Incr=-1; } /* set total level */ INLINE void set_tl(FM_CH *CH,FM_SLOT *SLOT , int v) { (void)CH; SLOT->tl = (v&0x7f)<<(ENV_BITS-7); /* 7bit TL */ /* recalculate EG output */ if ((SLOT->ssg&0x08) && (SLOT->ssgn ^ (SLOT->ssg&0x04)) && (SLOT->state > EG_REL)) SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; else SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; } /* set attack rate & key scale */ INLINE void set_ar_ksr(UINT8 type, FM_CH *CH,FM_SLOT *SLOT,int v) { (void)type; UINT8 old_KSR = SLOT->KSR; SLOT->ar = (v&0x1f) ? 32 + ((v&0x1f)<<1) : 0; SLOT->KSR = (UINT8)(3-(v>>6)); if (SLOT->KSR != old_KSR) { CH->SLOT[SLOT1].Incr=-1; } /* Even if it seems unnecessary to do it here, it could happen that KSR and KC */ /* are modified but the resulted SLOT->ksr value (kc >> SLOT->KSR) remains unchanged. */ /* In such case, Attack Rate would not be recalculated by "refresh_fc_eg_slot". */ /* This actually fixes the intro of "The Adventures of Batman & Robin" (Eke-Eke) */ if ((SLOT->ar + SLOT->ksr) < (32+62)) { SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; } else { /* verified by Nemesis on real hardware (Attack phase is blocked) */ SLOT->eg_sh_ar = 0; SLOT->eg_sel_ar = 18*RATE_STEPS; } } /* set decay rate */ INLINE void set_dr(UINT8 type, FM_SLOT *SLOT,int v) { (void)type; SLOT->d1r = (v&0x1f) ? 32 + ((v&0x1f)<<1) : 0; SLOT->eg_sh_d1r = eg_rate_shift [SLOT->d1r + SLOT->ksr]; SLOT->eg_sel_d1r= eg_rate_select[SLOT->d1r + SLOT->ksr]; } /* set sustain rate */ INLINE void set_sr(UINT8 type, FM_SLOT *SLOT,int v) { (void)type; SLOT->d2r = (v&0x1f) ? 32 + ((v&0x1f)<<1) : 0; SLOT->eg_sh_d2r = eg_rate_shift [SLOT->d2r + SLOT->ksr]; SLOT->eg_sel_d2r= eg_rate_select[SLOT->d2r + SLOT->ksr]; } /* set release rate */ INLINE void set_sl_rr(UINT8 type, FM_SLOT *SLOT,int v) { (void)type; SLOT->sl = sl_table[ v>>4 ]; /* check EG state changes */ if ((SLOT->state == EG_DEC) && (SLOT->volume >= (INT32)(SLOT->sl))) SLOT->state = EG_SUS; SLOT->rr = 34 + ((v&0x0f)<<2); SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr]; SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr]; } /* advance LFO to next sample */ INLINE void advance_lfo(FM_OPN *OPN) { if (OPN->lfo_timer_overflow) /* LFO enabled ? */ { /* increment LFO timer */ OPN->lfo_timer += OPN->lfo_timer_add; /* when LFO is enabled, one level will last for 108, 77, 71, 67, 62, 44, 8 or 5 samples */ while (OPN->lfo_timer >= OPN->lfo_timer_overflow) { OPN->lfo_timer -= OPN->lfo_timer_overflow; /* There are 128 LFO steps */ OPN->lfo_cnt = ( OPN->lfo_cnt + 1 ) & 127; /* triangle (inverted) */ /* AM: from 126 to 0 step -2, 0 to 126 step +2 */ if (OPN->lfo_cnt<64) OPN->LFO_AM = (OPN->lfo_cnt ^ 63) << 1; else OPN->LFO_AM = (OPN->lfo_cnt & 63) << 1; /* PM works with 4 times slower clock */ OPN->LFO_PM = OPN->lfo_cnt >> 2; } } } INLINE void advance_eg_channel(FM_OPN *OPN, FM_SLOT *SLOT) { //unsigned int out; unsigned int i = 4; /* four operators per channel */ do { switch(SLOT->state) { case EG_ATT: /* attack phase */ if (!(OPN->eg_cnt & ((1<eg_sh_ar)-1))) { /* update attenuation level */ SLOT->volume += (~SLOT->volume * (eg_inc[SLOT->eg_sel_ar + ((OPN->eg_cnt>>SLOT->eg_sh_ar)&7)]))>>4; /* check phase transition*/ if (SLOT->volume <= MIN_ATT_INDEX) { SLOT->volume = MIN_ATT_INDEX; SLOT->state = (SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC; /* special case where SL=0 */ } /* recalculate EG output */ if ((SLOT->ssg&0x08) && (SLOT->ssgn ^ (SLOT->ssg&0x04))) /* SSG-EG Output Inversion */ SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; else SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; } break; case EG_DEC: /* decay phase */ if (!(OPN->eg_cnt & ((1<eg_sh_d1r)-1))) { /* SSG EG type */ if (SLOT->ssg&0x08) { /* update attenuation level */ if (SLOT->volume < 0x200) { SLOT->volume += 4 * eg_inc[SLOT->eg_sel_d1r + ((OPN->eg_cnt>>SLOT->eg_sh_d1r)&7)]; /* recalculate EG output */ if (SLOT->ssgn ^ (SLOT->ssg&0x04)) /* SSG-EG Output Inversion */ SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; else SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; } } else { /* update attenuation level */ SLOT->volume += eg_inc[SLOT->eg_sel_d1r + ((OPN->eg_cnt>>SLOT->eg_sh_d1r)&7)]; /* recalculate EG output */ SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; } /* check phase transition*/ if (SLOT->volume >= (INT32)(SLOT->sl)) SLOT->state = EG_SUS; } break; case EG_SUS: /* sustain phase */ if (!(OPN->eg_cnt & ((1<eg_sh_d2r)-1))) { /* SSG EG type */ if (SLOT->ssg&0x08) { /* update attenuation level */ if (SLOT->volume < 0x200) { SLOT->volume += 4 * eg_inc[SLOT->eg_sel_d2r + ((OPN->eg_cnt>>SLOT->eg_sh_d2r)&7)]; /* recalculate EG output */ if (SLOT->ssgn ^ (SLOT->ssg&0x04)) /* SSG-EG Output Inversion */ SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; else SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; } } else { /* update attenuation level */ SLOT->volume += eg_inc[SLOT->eg_sel_d2r + ((OPN->eg_cnt>>SLOT->eg_sh_d2r)&7)]; /* check phase transition*/ if ( SLOT->volume >= MAX_ATT_INDEX ) SLOT->volume = MAX_ATT_INDEX; /* do not change SLOT->state (verified on real chip) */ /* recalculate EG output */ SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; } } break; case EG_REL: /* release phase */ if (!(OPN->eg_cnt & ((1<eg_sh_rr)-1))) { /* SSG EG type */ if (SLOT->ssg&0x08) { /* update attenuation level */ if (SLOT->volume < 0x200) SLOT->volume += 4 * eg_inc[SLOT->eg_sel_rr + ((OPN->eg_cnt>>SLOT->eg_sh_rr)&7)]; /* check phase transition */ if (SLOT->volume >= 0x200) { SLOT->volume = MAX_ATT_INDEX; SLOT->state = EG_OFF; } } else { /* update attenuation level */ SLOT->volume += eg_inc[SLOT->eg_sel_rr + ((OPN->eg_cnt>>SLOT->eg_sh_rr)&7)]; /* check phase transition*/ if (SLOT->volume >= MAX_ATT_INDEX) { SLOT->volume = MAX_ATT_INDEX; SLOT->state = EG_OFF; } } /* recalculate EG output */ SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; } break; } SLOT++; i--; }while (i); } /* SSG-EG update process */ /* The behavior is based upon Nemesis tests on real hardware */ /* This is actually executed before each samples */ INLINE void update_ssg_eg_channel(FM_SLOT *SLOT) { unsigned int i = 4; /* four operators per channel */ do { /* detect SSG-EG transition */ /* this is not required during release phase as the attenuation has been forced to MAX and output invert flag is not used */ /* if an Attack Phase is programmed, inversion can occur on each sample */ if ((SLOT->ssg & 0x08) && (SLOT->volume >= 0x200) && (SLOT->state > EG_REL)) { if (SLOT->ssg & 0x01) /* bit 0 = hold SSG-EG */ { /* set inversion flag */ if (SLOT->ssg & 0x02) SLOT->ssgn = 4; /* force attenuation level during decay phases */ if ((SLOT->state != EG_ATT) && !(SLOT->ssgn ^ (SLOT->ssg & 0x04))) SLOT->volume = MAX_ATT_INDEX; } else /* loop SSG-EG */ { /* toggle output inversion flag or reset Phase Generator */ if (SLOT->ssg & 0x02) SLOT->ssgn ^= 4; else SLOT->phase = 0; /* same as Key ON */ if (SLOT->state != EG_ATT) { if ((SLOT->ar + SLOT->ksr) < 94 /*32+62*/) { SLOT->state = (SLOT->volume <= MIN_ATT_INDEX) ? ((SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC) : EG_ATT; } else { /* Attack Rate is maximal: directly switch to Decay or Substain */ SLOT->volume = MIN_ATT_INDEX; SLOT->state = (SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC; } } } /* recalculate EG output */ if (SLOT->ssgn ^ (SLOT->ssg&0x04)) SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; else SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; } /* next slot */ SLOT++; i--; } while (i); } INLINE void update_phase_lfo_slot(FM_OPN *OPN, FM_SLOT *SLOT, INT32 pms, UINT32 block_fnum) { UINT32 fnum_lfo = ((block_fnum & 0x7f0) >> 4) * 32 * 8; INT32 lfo_fn_table_index_offset = lfo_pm_table[ fnum_lfo + pms + OPN->LFO_PM ]; block_fnum = block_fnum*2 + lfo_fn_table_index_offset; if (lfo_fn_table_index_offset) /* LFO phase modulation active */ { UINT8 blk = (block_fnum&0x7000) >> 12; UINT32 fn = block_fnum & 0xfff; /* recalculate keyscale code */ /*int kc = (blk<<2) | opn_fktable[fn >> 7];*/ /* This really stupid bug caused a read outside of the array [size 0x10] and returned invalid values. This caused an annoying vibrato for some notes. (Note: seems to be a copy-and-paste from OPNWriteReg -> case 0xA0) Why are MAME cores always SOO buggy ?! */ /* Oh, and before I forget: it's correct in fm.c */ int kc = (blk<<2) | opn_fktable[fn >> 8]; /* Thanks to Blargg - his patch that helped me to find this bug */ /* recalculate (frequency) phase increment counter */ int fc = (OPN->fn_table[fn]>>(7-blk)) + SLOT->DT[kc]; /* (frequency) phase overflow (credits to Nemesis) */ if (fc < 0) fc += OPN->fn_max; /* update phase */ SLOT->phase += (fc * SLOT->mul) >> 1; } else /* LFO phase modulation = zero */ { SLOT->phase += SLOT->Incr; } } INLINE void update_phase_lfo_channel(FM_OPN *OPN, FM_CH *CH) { UINT32 block_fnum = CH->block_fnum; UINT32 fnum_lfo = ((block_fnum & 0x7f0) >> 4) * 32 * 8; INT32 lfo_fn_table_index_offset = lfo_pm_table[ fnum_lfo + CH->pms + OPN->LFO_PM ]; block_fnum = block_fnum*2 + lfo_fn_table_index_offset; if (lfo_fn_table_index_offset) /* LFO phase modulation active */ { UINT8 blk = (block_fnum&0x7000) >> 12; UINT32 fn = block_fnum & 0xfff; /* recalculate keyscale code */ /*int kc = (blk<<2) | opn_fktable[fn >> 7];*/ /* the same stupid bug as above */ int kc = (blk<<2) | opn_fktable[fn >> 8]; /* recalculate (frequency) phase increment counter */ int fc = (OPN->fn_table[fn]>>(7-blk)); /* (frequency) phase overflow (credits to Nemesis) */ int finc = fc + CH->SLOT[SLOT1].DT[kc]; if (finc < 0) finc += OPN->fn_max; CH->SLOT[SLOT1].phase += (finc*CH->SLOT[SLOT1].mul) >> 1; finc = fc + CH->SLOT[SLOT2].DT[kc]; if (finc < 0) finc += OPN->fn_max; CH->SLOT[SLOT2].phase += (finc*CH->SLOT[SLOT2].mul) >> 1; finc = fc + CH->SLOT[SLOT3].DT[kc]; if (finc < 0) finc += OPN->fn_max; CH->SLOT[SLOT3].phase += (finc*CH->SLOT[SLOT3].mul) >> 1; finc = fc + CH->SLOT[SLOT4].DT[kc]; if (finc < 0) finc += OPN->fn_max; CH->SLOT[SLOT4].phase += (finc*CH->SLOT[SLOT4].mul) >> 1; } else /* LFO phase modulation = zero */ { CH->SLOT[SLOT1].phase += CH->SLOT[SLOT1].Incr; CH->SLOT[SLOT2].phase += CH->SLOT[SLOT2].Incr; CH->SLOT[SLOT3].phase += CH->SLOT[SLOT3].Incr; CH->SLOT[SLOT4].phase += CH->SLOT[SLOT4].Incr; } } /* update phase increment and envelope generator */ INLINE void refresh_fc_eg_slot(FM_OPN *OPN, FM_SLOT *SLOT , int fc , int kc ) { int ksr = kc >> SLOT->KSR; fc += SLOT->DT[kc]; /* detects frequency overflow (credits to Nemesis) */ if (fc < 0) fc += OPN->fn_max; /* (frequency) phase increment counter */ SLOT->Incr = (fc * SLOT->mul) >> 1; if( SLOT->ksr != ksr ) { SLOT->ksr = (UINT8)ksr; /* calculate envelope generator rates */ if ((SLOT->ar + SLOT->ksr) < 32+62) { SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; } else { SLOT->eg_sh_ar = 0; SLOT->eg_sel_ar = 18*RATE_STEPS; /* verified by Nemesis on real hardware (Attack phase is blocked) */ } SLOT->eg_sh_d1r = eg_rate_shift [SLOT->d1r + SLOT->ksr]; SLOT->eg_sh_d2r = eg_rate_shift [SLOT->d2r + SLOT->ksr]; SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr]; SLOT->eg_sel_d1r= eg_rate_select[SLOT->d1r + SLOT->ksr]; SLOT->eg_sel_d2r= eg_rate_select[SLOT->d2r + SLOT->ksr]; SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr]; } } /* update phase increment counters */ INLINE void refresh_fc_eg_chan(FM_OPN *OPN, FM_CH *CH ) { if( CH->SLOT[SLOT1].Incr==-1) { int fc = CH->fc; int kc = CH->kcode; refresh_fc_eg_slot(OPN, &CH->SLOT[SLOT1] , fc , kc ); refresh_fc_eg_slot(OPN, &CH->SLOT[SLOT2] , fc , kc ); refresh_fc_eg_slot(OPN, &CH->SLOT[SLOT3] , fc , kc ); refresh_fc_eg_slot(OPN, &CH->SLOT[SLOT4] , fc , kc ); } } #define volume_calc(OP) ((OP)->vol_out + (AM & (OP)->AMmask)) INLINE signed int op_calc(UINT32 phase, unsigned int env, signed int pm) { UINT32 p; p = (env<<3) + sin_tab[ ( ((signed int)((phase & ~FREQ_MASK) + (pm<<15))) >> FREQ_SH ) & SIN_MASK ]; if (p >= TL_TAB_LEN) return 0; return tl_tab[p]; } INLINE signed int op_calc1(UINT32 phase, unsigned int env, signed int pm) { UINT32 p; p = (env<<3) + sin_tab[ ( ((signed int)((phase & ~FREQ_MASK) + pm )) >> FREQ_SH ) & SIN_MASK ]; if (p >= TL_TAB_LEN) return 0; return tl_tab[p]; } INLINE void chan_calc(FM_OPN *OPN, FM_CH *CH, int chnum) { UINT32 AM = OPN->LFO_AM >> CH->ams; unsigned int eg_out; if (CH->Muted) return; OPN->m2 = OPN->c1 = OPN->c2 = OPN->mem = 0; *CH->mem_connect = CH->mem_value; /* restore delayed sample (MEM) value to m2 or c2 */ eg_out = volume_calc(&CH->SLOT[SLOT1]); { INT32 out = CH->op1_out[0] + CH->op1_out[1]; CH->op1_out[0] = CH->op1_out[1]; if( !CH->connect1 ) { /* algorithm 5 */ OPN->mem = OPN->c1 = OPN->c2 = CH->op1_out[0]; } else { /* other algorithms */ *CH->connect1 += CH->op1_out[0]; } CH->op1_out[1] = 0; if( eg_out < ENV_QUIET ) /* SLOT 1 */ { if (!CH->FB) out=0; CH->op1_out[1] = op_calc1(CH->SLOT[SLOT1].phase, eg_out, (out<FB) ); } } eg_out = volume_calc(&CH->SLOT[SLOT3]); if( eg_out < ENV_QUIET ) /* SLOT 3 */ *CH->connect3 += op_calc(CH->SLOT[SLOT3].phase, eg_out, OPN->m2); eg_out = volume_calc(&CH->SLOT[SLOT2]); if( eg_out < ENV_QUIET ) /* SLOT 2 */ *CH->connect2 += op_calc(CH->SLOT[SLOT2].phase, eg_out, OPN->c1); eg_out = volume_calc(&CH->SLOT[SLOT4]); if( eg_out < ENV_QUIET ) /* SLOT 4 */ *CH->connect4 += op_calc(CH->SLOT[SLOT4].phase, eg_out, OPN->c2); /* store current MEM */ CH->mem_value = OPN->mem; /* update phase counters AFTER output calculations */ if(CH->pms) { /* add support for 3 slot mode */ if ((OPN->ST.mode & 0xC0) && (chnum == 2)) { update_phase_lfo_slot(OPN, &CH->SLOT[SLOT1], CH->pms, OPN->SL3.block_fnum[1]); update_phase_lfo_slot(OPN, &CH->SLOT[SLOT2], CH->pms, OPN->SL3.block_fnum[2]); update_phase_lfo_slot(OPN, &CH->SLOT[SLOT3], CH->pms, OPN->SL3.block_fnum[0]); update_phase_lfo_slot(OPN, &CH->SLOT[SLOT4], CH->pms, CH->block_fnum); } else update_phase_lfo_channel(OPN, CH); } else /* no LFO phase modulation */ { CH->SLOT[SLOT1].phase += CH->SLOT[SLOT1].Incr; CH->SLOT[SLOT2].phase += CH->SLOT[SLOT2].Incr; CH->SLOT[SLOT3].phase += CH->SLOT[SLOT3].Incr; CH->SLOT[SLOT4].phase += CH->SLOT[SLOT4].Incr; } } /* CSM Key Controll */ INLINE void CSMKeyControll(FM_OPN *OPN, FM_CH *CH) { /* all key ON (verified by Nemesis on real hardware) */ FM_KEYON_CSM(OPN,CH,SLOT1); FM_KEYON_CSM(OPN,CH,SLOT2); FM_KEYON_CSM(OPN,CH,SLOT3); FM_KEYON_CSM(OPN,CH,SLOT4); OPN->SL3.key_csm = 1; } #if BUILD_OPN /* write a OPN mode register 0x20-0x2f */ static void OPNWriteMode(FM_OPN *OPN, int r, int v) { UINT8 c; FM_CH *CH; switch(r) { case 0x21: /* Test */ break; case 0x22: /* LFO FREQ (YM2608/YM2610/YM2610B/YM2612) */ if( !(OPN->type & TYPE_LFOPAN) ) break; if (v&8) /* LFO enabled ? */ { OPN->lfo_timer_overflow = lfo_samples_per_step[v&7] << LFO_SH; } else { /* hold LFO waveform in reset state */ OPN->lfo_timer_overflow = 0; OPN->lfo_timer = 0; OPN->lfo_cnt = 0; OPN->LFO_PM = 0; OPN->LFO_AM = 126; } break; case 0x24: /* timer A High 8*/ OPN->ST.TA = (OPN->ST.TA & 0x03)|(((int)v)<<2); break; case 0x25: /* timer A Low 2*/ OPN->ST.TA = (OPN->ST.TA & 0x3fc)|(v&3); break; case 0x26: /* timer B */ OPN->ST.TB = (UINT8)v; break; case 0x27: /* mode, timer control */ set_timers( OPN, &(OPN->ST),OPN->ST.param,v ); break; case 0x28: /* key on / off */ c = v & 0x03; if( c == 3 ) break; if( (v&0x04) && (OPN->type & TYPE_6CH) ) c+=3; CH = OPN->P_CH; CH = &CH[c]; if(v&0x10) FM_KEYON(OPN,CH,SLOT1); else FM_KEYOFF(OPN,CH,SLOT1); if(v&0x20) FM_KEYON(OPN,CH,SLOT2); else FM_KEYOFF(OPN,CH,SLOT2); if(v&0x40) FM_KEYON(OPN,CH,SLOT3); else FM_KEYOFF(OPN,CH,SLOT3); if(v&0x80) FM_KEYON(OPN,CH,SLOT4); else FM_KEYOFF(OPN,CH,SLOT4); break; } } /* write a OPN register (0x30-0xff) */ static void OPNWriteReg(FM_OPN *OPN, int r, int v) { FM_CH *CH; FM_SLOT *SLOT; UINT8 c = OPN_CHAN(r); if (c == 3) return; /* 0xX3,0xX7,0xXB,0xXF */ if (r >= 0x100) c+=3; CH = OPN->P_CH; CH = &CH[c]; SLOT = &(CH->SLOT[OPN_SLOT(r)]); switch( r & 0xf0 ) { case 0x30: /* DET , MUL */ set_det_mul(&OPN->ST,CH,SLOT,v); break; case 0x40: /* TL */ set_tl(CH,SLOT,v); break; case 0x50: /* KS, AR */ set_ar_ksr(OPN->type,CH,SLOT,v); break; case 0x60: /* bit7 = AM ENABLE, DR */ set_dr(OPN->type, SLOT,v); if(OPN->type & TYPE_LFOPAN) /* YM2608/2610/2610B/2612 */ { SLOT->AMmask = (v&0x80) ? ~0 : 0; } break; case 0x70: /* SR */ set_sr(OPN->type,SLOT,v); break; case 0x80: /* SL, RR */ set_sl_rr(OPN->type,SLOT,v); break; case 0x90: /* SSG-EG */ SLOT->ssg = v&0x0f; /* recalculate EG output */ if (SLOT->state > EG_REL) { if ((SLOT->ssg&0x08) && (SLOT->ssgn ^ (SLOT->ssg&0x04))) SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; else SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; } /* SSG-EG envelope shapes : E AtAlH 1 0 0 0 \\\\ 1 0 0 1 \___ 1 0 1 0 \/\/ ___ 1 0 1 1 \ 1 1 0 0 //// ___ 1 1 0 1 / 1 1 1 0 /\/\ 1 1 1 1 /___ E = SSG-EG enable The shapes are generated using Attack, Decay and Sustain phases. Each single character in the diagrams above represents this whole sequence: - when KEY-ON = 1, normal Attack phase is generated (*without* any difference when compared to normal mode), - later, when envelope level reaches minimum level (max volume), the EG switches to Decay phase (which works with bigger steps when compared to normal mode - see below), - later when envelope level passes the SL level, the EG swithes to Sustain phase (which works with bigger steps when compared to normal mode - see below), - finally when envelope level reaches maximum level (min volume), the EG switches to Attack phase again (depends on actual waveform). Important is that when switch to Attack phase occurs, the phase counter of that operator will be zeroed-out (as in normal KEY-ON) but not always. (I havent found the rule for that - perhaps only when the output level is low) The difference (when compared to normal Envelope Generator mode) is that the resolution in Decay and Sustain phases is 4 times lower; this results in only 256 steps instead of normal 1024. In other words: when SSG-EG is disabled, the step inside of the EG is one, when SSG-EG is enabled, the step is four (in Decay and Sustain phases). Times between the level changes are the same in both modes. Important: Decay 1 Level (so called SL) is compared to actual SSG-EG output, so it is the same in both SSG and no-SSG modes, with this exception: when the SSG-EG is enabled and is generating raising levels (when the EG output is inverted) the SL will be found at wrong level !!! For example, when SL=02: 0 -6 = -6dB in non-inverted EG output 96-6 = -90dB in inverted EG output Which means that EG compares its level to SL as usual, and that the output is simply inverted afterall. The Yamaha's manuals say that AR should be set to 0x1f (max speed). That is not necessary, but then EG will be generating Attack phase. */ break; case 0xa0: switch( OPN_SLOT(r) ) { case 0: /* 0xa0-0xa2 : FNUM1 */ if (OPN->LegacyMode) OPN->ST.fn_h = (UINT8)(CH->block_fnum >> 8); { UINT32 fn = (((UINT32)( (OPN->ST.fn_h)&7))<<8) + v; UINT8 blk = OPN->ST.fn_h>>3; /* keyscale code */ CH->kcode = (blk<<2) | opn_fktable[fn >> 7]; /* phase increment counter */ CH->fc = OPN->fn_table[fn*2]>>(7-blk); /* store fnum in clear form for LFO PM calculations */ CH->block_fnum = (blk<<11) | fn; CH->SLOT[SLOT1].Incr=-1; } break; case 1: /* 0xa4-0xa6 : FNUM2,BLK */ OPN->ST.fn_h = v&0x3f; if (OPN->LegacyMode) // behave like Gens (workaround for stupid Kega Fusion init block) CH->block_fnum = (OPN->ST.fn_h << 8) | (CH->block_fnum & 0xFF); break; case 2: /* 0xa8-0xaa : 3CH FNUM1 */ if (OPN->LegacyMode) OPN->SL3.fn_h = (UINT8)(OPN->SL3.block_fnum[c] >> 8); if(r < 0x100) { UINT32 fn = (((UINT32)(OPN->SL3.fn_h&7))<<8) + v; UINT8 blk = OPN->SL3.fn_h>>3; /* keyscale code */ OPN->SL3.kcode[c]= (blk<<2) | opn_fktable[fn >> 7]; /* phase increment counter */ OPN->SL3.fc[c] = OPN->fn_table[fn*2]>>(7-blk); OPN->SL3.block_fnum[c] = (blk<<11) | fn; (OPN->P_CH)[2].SLOT[SLOT1].Incr=-1; } break; case 3: /* 0xac-0xae : 3CH FNUM2,BLK */ if(r < 0x100) { OPN->SL3.fn_h = v&0x3f; if (OPN->LegacyMode) OPN->SL3.block_fnum[c] = (OPN->SL3.fn_h << 8) | (OPN->SL3.block_fnum[c] & 0xFF); } break; } break; case 0xb0: switch( OPN_SLOT(r) ) { case 0: /* 0xb0-0xb2 : FB,ALGO */ { int feedback = (v>>3)&7; CH->ALGO = v&7; CH->FB = (UINT8)(feedback ? feedback+6 : 0); setup_connection( OPN, CH, c ); } break; case 1: /* 0xb4-0xb6 : L , R , AMS , PMS (YM2612/YM2610B/YM2610/YM2608) */ if( OPN->type & TYPE_LFOPAN) { /* b0-2 PMS */ CH->pms = (v & 7) * 32; /* CH->pms = PM depth * 32 (index in lfo_pm_table) */ /* b4-5 AMS */ CH->ams = lfo_ams_depth_shift[(v>>4) & 0x03]; /* PAN : b7 = L, b6 = R */ OPN->pan[ c*2 ] = (v & 0x80) ? ~0 : 0; OPN->pan[ c*2+1 ] = (v & 0x40) ? ~0 : 0; } break; } break; } } /* initialize time tables */ static void init_timetables(FM_OPN *OPN) { int i,d; double rate; #if 0 logerror("FM.C: samplerate=%8i chip clock=%8i freqbase=%f \n", ST->rate, ST->clock, ST->freqbase ); #endif /* DeTune table */ for (d = 0;d <= 3;d++) { for (i = 0;i <= 31;i++) { rate = ((double)dt_tab[d*32 + i]) * OPN->ST.freqbase * (1<<(FREQ_SH-10)); /* -10 because chip works with 10.10 fixed point, while we use 16.16 */ OPN->ST.dt_tab[d][i] = (INT32) rate; OPN->ST.dt_tab[d+4][i] = -OPN->ST.dt_tab[d][i]; #if 0 logerror("FM.C: DT [%2i %2i] = %8x \n", d, i, ST->dt_tab[d][i] ); #endif } } /* there are 2048 FNUMs that can be generated using FNUM/BLK registers but LFO works with one more bit of a precision so we really need 4096 elements */ /* calculate fnumber -> increment counter table */ for(i = 0; i < 4096; i++) { /* freq table for octave 7 */ /* OPN phase increment counter = 20bit */ /* the correct formula is : F-Number = (144 * fnote * 2^20 / M) / 2^(B-1) */ /* where sample clock is M/144 */ /* this means the increment value for one clock sample is FNUM * 2^(B-1) = FNUM * 64 for octave 7 */ /* we also need to handle the ratio between the chip frequency and the emulated frequency (can be 1.0) */ OPN->fn_table[i] = (UINT32)( (double)i * 32 * OPN->ST.freqbase * (1<<(FREQ_SH-10)) ); /* -10 because chip works with 10.10 fixed point, while we use 16.16 */ } /* maximal frequency is required for Phase overflow calculation, register size is 17 bits (Nemesis) */ OPN->fn_max = (UINT32)( (double)0x20000 * OPN->ST.freqbase * (1<<(FREQ_SH-10)) ); } /* prescaler set (and make time tables) */ static void OPNSetPres(FM_OPN *OPN, int pres, int timer_prescaler, int SSGpres) { if (OPN->smpRateNative && OPN->smpRateFunc != NULL) { OPN->ST.rate = OPN->ST.clock / pres; if (OPN->smpRateFunc != NULL) OPN->smpRateFunc(OPN->smpRateData, OPN->ST.rate); } /* frequency base */ OPN->ST.freqbase = (OPN->ST.rate) ? ((double)OPN->ST.clock / OPN->ST.rate) / pres : 0; if (fabs(OPN->ST.freqbase - 1.0) < 0.00005) OPN->ST.freqbase = 1.0; #if 0 OPN->ST.rate = (double)OPN->ST.clock / pres; OPN->ST.freqbase = 1.0; #endif /* EG is updated every 3 samples */ OPN->eg_timer_add = (UINT32)((1<ST.freqbase); OPN->eg_timer_overflow = ( 3 ) * (1<lfo_timer_add = (UINT32)((1<ST.freqbase); /* Timer base time */ OPN->ST.timer_prescaler = timer_prescaler; /* SSG part prescaler set */ if( SSGpres ) OPN->ST.SSG_funcs.set_clock( OPN->ST.SSG_param, OPN->ST.clock * 2 / SSGpres ); /* make time tables */ init_timetables( OPN ); } static void reset_channels( FM_ST *ST , FM_CH *CH , int num ) { int c,s; ST->mode = 0; /* normal mode */ for( c = 0 ; c < num ; c++ ) { //memset(&CH[c], 0x00, sizeof(FM_CH)); CH[c].mem_value = 0; CH[c].op1_out[0] = 0; CH[c].op1_out[1] = 0; CH[c].fc = 0; for(s = 0 ; s < 4 ; s++ ) { //memset(&CH[c].SLOT[s], 0x00, sizeof(FM_SLOT)); CH[c].SLOT[s].DT = ST->dt_tab[0]; CH[c].SLOT[s].Incr = -1; CH[c].SLOT[s].key = 0; CH[c].SLOT[s].phase = 0; CH[c].SLOT[s].ssg = 0; CH[c].SLOT[s].ssgn = 0; CH[c].SLOT[s].state= EG_OFF; CH[c].SLOT[s].volume = MAX_ATT_INDEX; CH[c].SLOT[s].vol_out= MAX_ATT_INDEX; } } } /* initialize generic tables */ static void init_tables(void) { signed int i,x; signed int n; double o,m; if (tablesInit) return; tablesInit = 1; /* build Linear Power Table */ for (x=0; x>= 4; /* 12 bits here */ if (n&1) /* round to nearest */ n = (n>>1)+1; else n = n>>1; /* 11 bits here (rounded) */ n <<= 2; /* 13 bits here (as in real chip) */ /* 14 bits (with sign bit) */ tl_tab[ x*2 + 0 ] = n; tl_tab[ x*2 + 1 ] = -tl_tab[ x*2 + 0 ]; /* one entry in the 'Power' table use the following format, xxxxxyyyyyyyys with: */ /* s = sign bit */ /* yyyyyyyy = 8-bits decimal part (0-TL_RES_LEN) */ /* xxxxx = 5-bits integer 'shift' value (0-31) but, since Power table output is 13 bits, */ /* any value above 13 (included) would be discarded. */ for (i=1; i<13; i++) { tl_tab[ x*2+0 + i*2*TL_RES_LEN ] = tl_tab[ x*2+0 ]>>i; tl_tab[ x*2+1 + i*2*TL_RES_LEN ] = -tl_tab[ x*2+0 + i*2*TL_RES_LEN ]; } #if 0 logerror("tl %04i", x); for (i=0; i<13; i++) logerror(", [%02i] %4x", i*2, tl_tab[ x*2 /*+1*/ + i*2*TL_RES_LEN ]); logerror("\n"); #endif } /*logerror("FM.C: TL_TAB_LEN = %i elements (%i bytes)\n",TL_TAB_LEN, (int)sizeof(tl_tab));*/ /* build Logarithmic Sinus table */ for (i=0; i0.0) o = 8*log(1.0/m)/log(2.0); /* convert to 'decibels' */ else o = 8*log(-1.0/m)/log(2.0); /* convert to 'decibels' */ o = o / (ENV_STEP/4); n = (int)(2.0*o); if (n&1) /* round to nearest */ n = (n>>1)+1; else n = n>>1; /* 13-bits (8.5) value is formatted for above 'Power' table */ sin_tab[ i ] = n*2 + (m>=0.0? 0: 1 ); /*logerror("FM.C: sin [%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[i],tl_tab[sin_tab[i]]);*/ } /*logerror("FM.C: ENV_QUIET= %08x\n",ENV_QUIET );*/ /* build LFO PM modulation table */ for(i = 0; i < 8; i++) /* 8 PM depths */ { UINT8 fnum; for (fnum=0; fnum<128; fnum++) /* 7 bits meaningful of F-NUMBER */ { UINT8 value; UINT8 step; UINT32 offset_depth = i; UINT32 offset_fnum_bit; UINT32 bit_tmp; for (step=0; step<8; step++) { value = 0; for (bit_tmp=0; bit_tmp<7; bit_tmp++) /* 7 bits */ { if (fnum & (1<ST.prescaler_sel = 2; break; case 1: /* when postload */ break; case 0x2d: /* divider sel : select 1/1 for 1/3line */ OPN->ST.prescaler_sel |= 0x02; break; case 0x2e: /* divider sel , select 1/3line for output */ OPN->ST.prescaler_sel |= 0x01; break; case 0x2f: /* divider sel , clear both selector to 1/2,1/2 */ OPN->ST.prescaler_sel = 0; break; } sel = OPN->ST.prescaler_sel & 3; /* update prescaler */ OPNSetPres( OPN, opn_pres[sel]*pre_divider, opn_pres[sel]*pre_divider, ssg_pres[sel]*pre_divider ); } #endif /* BUILD_OPN_PRESCALER */ static void ssgdummy_set_clock(void* param, UINT32 clock) { (void)param; (void)clock; return; } static void ssgdummy_write(void* param, UINT8 address, UINT8 data) { (void)param; (void)address; (void)data; return; } static UINT8 ssgdummy_read(void* param) { (void)param; return 0x00; } static void ssgdummy_reset(void* param) { (void)param; return; } static const ssg_callbacks ssg_dummy_funcs = { ssgdummy_set_clock, ssgdummy_write, ssgdummy_read, ssgdummy_reset }; static void OPNLinkSSG(FM_OPN *OPN, const ssg_callbacks *ssg_cb, void *ssg_param) { if (ssg_cb == NULL) { OPN->ST.SSG_funcs = ssg_dummy_funcs; OPN->ST.SSG_param = NULL; } else { OPN->ST.SSG_funcs = *ssg_cb; OPN->ST.SSG_param = ssg_param; } return; } static void OPNCheckNativeSampleRate(FM_OPN *OPN) { UINT32 pres; UINT32 nativeSRate; int sRateDiff; pres = 6*12; // default prescaler if (OPN->type & TYPE_6CH) pres *= 2; nativeSRate = OPN->ST.clock / pres; sRateDiff = (int)OPN->ST.rate - (int)nativeSRate; OPN->smpRateNative = (abs(sRateDiff) <= 2); return; } static void OPNSetSmplRateChgCallback(FM_OPN *OPN, DEVCB_SRATE_CHG cbFunc, void* dataPtr) { // set Sample Rate Change Callback routine OPN->smpRateFunc = cbFunc; OPN->smpRateData = dataPtr; return; } #if BUILD_YM2203 /*****************************************************************************/ /* YM2203 local section */ /*****************************************************************************/ /* here's the virtual YM2203(OPN) */ typedef struct { DEV_DATA _devData; UINT8 REGS[256]; /* registers */ FM_OPN OPN; /* OPN state */ FM_CH CH[3]; /* channel state */ } YM2203; /* Generate samples for one of the YM2203s */ void ym2203_update_one(void *chip, UINT32 length, DEV_SMPL **buffer) { YM2203 *F2203 = (YM2203 *)chip; FM_OPN *OPN = &F2203->OPN; UINT32 i; DEV_SMPL *bufL,*bufR; FM_CH *cch[3]; if (buffer != NULL) { bufL = buffer[0]; bufR = buffer[1]; } else { bufL = bufR = NULL; } cch[0] = &F2203->CH[0]; cch[1] = &F2203->CH[1]; cch[2] = &F2203->CH[2]; /* refresh PG and EG */ refresh_fc_eg_chan( OPN, cch[0] ); refresh_fc_eg_chan( OPN, cch[1] ); if( (OPN->ST.mode & 0xc0) ) { /* 3SLOT MODE */ if( cch[2]->SLOT[SLOT1].Incr==-1) { refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT1] , OPN->SL3.fc[1] , OPN->SL3.kcode[1] ); refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT2] , OPN->SL3.fc[2] , OPN->SL3.kcode[2] ); refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT3] , OPN->SL3.fc[0] , OPN->SL3.kcode[0] ); refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT4] , cch[2]->fc , cch[2]->kcode ); } } else refresh_fc_eg_chan( OPN, cch[2] ); if (! length) { update_ssg_eg_channel(&cch[0]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[1]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[2]->SLOT[SLOT1]); } /* YM2203 doesn't have LFO so we must keep these globals at 0 level */ OPN->LFO_AM = 0; OPN->LFO_PM = 0; /* buffering */ for (i=0; i < length ; i++) { /* clear outputs */ OPN->out_fm[0] = 0; OPN->out_fm[1] = 0; OPN->out_fm[2] = 0; /* update SSG-EG output */ update_ssg_eg_channel(&cch[0]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[1]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[2]->SLOT[SLOT1]); /* calculate FM */ chan_calc(OPN, cch[0], 0 ); chan_calc(OPN, cch[1], 1 ); chan_calc(OPN, cch[2], 2 ); /* advance envelope generator */ OPN->eg_timer += OPN->eg_timer_add; while (OPN->eg_timer >= OPN->eg_timer_overflow) { OPN->eg_timer -= OPN->eg_timer_overflow; OPN->eg_cnt++; advance_eg_channel(OPN, &cch[0]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[1]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[2]->SLOT[SLOT1]); } /* buffering */ { DEV_SMPL lt; lt = OPN->out_fm[0] + OPN->out_fm[1] + OPN->out_fm[2]; /* buffering */ bufL[i] = lt; bufR[i] = lt; } /* CSM mode: if CSM Key ON has occured, CSM Key OFF need to be sent */ /* only if Timer A does not overflow again (i.e CSM Key ON not set again) */ OPN->SL3.key_csm <<= 1; /* timer A control */ INTERNAL_TIMER_A( &OPN->ST , cch[2] ) /* CSM Mode Key ON still disabled */ if (OPN->SL3.key_csm & 2) { /* CSM Mode Key OFF (verified by Nemesis on real hardware) */ FM_KEYOFF_CSM(cch[2],SLOT1); FM_KEYOFF_CSM(cch[2],SLOT2); FM_KEYOFF_CSM(cch[2],SLOT3); FM_KEYOFF_CSM(cch[2],SLOT4); OPN->SL3.key_csm = 0; } } /* timer B control */ INTERNAL_TIMER_B(&OPN->ST,length) } static void ym2203_update_req(void *param) { ym2203_update_one(param, 0, NULL); } /* ---------- reset one of chip ---------- */ void ym2203_reset_chip(void *chip) { int i; YM2203 *F2203 = (YM2203 *)chip; FM_OPN *OPN = &F2203->OPN; /* Reset Prescaler */ OPNPrescaler_w(OPN, 0 , 1 ); /* reset SSG section */ OPN->ST.SSG_funcs.reset(OPN->ST.SSG_param); /* status clear */ FM_IRQMASK_SET(&OPN->ST,0x03); FM_BUSY_CLEAR(&OPN->ST); OPN->eg_timer = 0; OPN->eg_cnt = 0; OPN->ST.TAC = 0; OPN->ST.TBC = 0; OPN->SL3.key_csm = 0; OPN->ST.status = 0; OPN->ST.mode = 0; OPN->ST.irq = 0; memset(F2203->REGS, 0x00, sizeof(UINT8) * 256); reset_channels( &OPN->ST , F2203->CH , 3 ); /* reset Operator paramater */ for(i = 0xb2 ; i >= 0x30 ; i-- ) OPNWriteReg(OPN,i,0); OPNWriteMode(OPN,0x27,0x30); /* mode 0 , timer reset */ for(i = 0x26 ; i >= 0x20 ; i-- ) OPNWriteReg(OPN,i,0); } /* ---------- Initialize YM2203 emulator(s) ---------- 'num' is the number of virtual YM2203s to allocate 'clock' is the chip clock in Hz 'rate' is sampling rate */ void * ym2203_init(void *param, UINT32 clock, UINT32 rate, FM_TIMERHANDLER timer_handler,FM_IRQHANDLER IRQHandler) { YM2203 *F2203; /* allocate ym2203 state space */ F2203 = (YM2203 *)calloc(1,sizeof(YM2203)); if( F2203==NULL) return NULL; /* allocate total level table (128kb space) */ init_tables(); F2203->OPN.ST.param = param; F2203->OPN.type = TYPE_YM2203; F2203->OPN.P_CH = F2203->CH; F2203->OPN.ST.clock = clock; F2203->OPN.ST.rate = rate; OPNCheckNativeSampleRate(&F2203->OPN); F2203->OPN.ST.timer_handler = timer_handler; F2203->OPN.ST.IRQ_Handler = IRQHandler; OPNLinkSSG(&F2203->OPN, NULL, NULL); OPNSetSmplRateChgCallback(&F2203->OPN, NULL, NULL); ym2203_set_mutemask(F2203, 0x00); return F2203; } /* link SSG emulator */ void ym2203_link_ssg(void *chip, const ssg_callbacks *ssg, void *ssg_param) { YM2203 *F2203 = (YM2203 *)chip; OPNLinkSSG(&F2203->OPN, ssg, ssg_param); OPNPrescaler_w(&F2203->OPN, 1, 1); return; } /* set sample rate change callback */ void ym2203_set_srchg_cb(void *chip, DEVCB_SRATE_CHG cbFunc, void* dataPtr) { YM2203 *F2203 = (YM2203 *)chip; OPNSetSmplRateChgCallback(&F2203->OPN, cbFunc, dataPtr); return; } /* shut down emulator */ void ym2203_shutdown(void *chip) { YM2203 *F2203 = (YM2203 *)chip; free(F2203); } /* YM2203 I/O interface */ void ym2203_write(void *chip,UINT8 a,UINT8 v) { YM2203 *F2203 = (YM2203 *)chip; FM_OPN *OPN = &F2203->OPN; if (a>=2)return; if( !(a&1) ) { /* address port */ OPN->ST.address = v; /* Write register to SSG emulator */ if( v < 16 ) OPN->ST.SSG_funcs.write(OPN->ST.SSG_param,a,v); /* prescaler select : 2d,2e,2f */ if( v >= 0x2d && v <= 0x2f ) OPNPrescaler_w(OPN , v , 1); } else { /* data port */ UINT8 addr = OPN->ST.address; F2203->REGS[addr] = v; switch( addr & 0xf0 ) { case 0x00: /* 0x00-0x0f : SSG section */ /* Write data to SSG emulator */ OPN->ST.SSG_funcs.write(OPN->ST.SSG_param,a,v); break; case 0x20: /* 0x20-0x2f : Mode section */ ym2203_update_req(F2203); /* write register */ OPNWriteMode(OPN,addr,v); break; default: /* 0x30-0xff : OPN section */ ym2203_update_req(F2203); /* write register */ OPNWriteReg(OPN,addr,v); } FM_BUSY_SET(&OPN->ST,1); } //return OPN->ST.irq; return; } UINT8 ym2203_read(void *chip,UINT8 a) { YM2203 *F2203 = (YM2203 *)chip; UINT8 addr = F2203->OPN.ST.address; UINT8 ret = 0; if( !(a&1) ) { /* status port */ ret = FM_STATUS_FLAG(&F2203->OPN.ST); } else { /* data port (only SSG) */ if( addr < 16 ) ret = F2203->OPN.ST.SSG_funcs.read(F2203->OPN.ST.SSG_param, 0); } return ret; } UINT8 ym2203_timer_over(void *chip,UINT8 c) { YM2203 *F2203 = (YM2203 *)chip; if( c ) { /* Timer B */ TimerBOver( &(F2203->OPN.ST) ); } else { /* Timer A */ ym2203_update_req(F2203); /* timer update */ TimerAOver( &(F2203->OPN.ST) ); /* CSM mode key,TL controll */ if ((F2203->OPN.ST.mode & 0xc0) == 0x80) { /* CSM mode total level latch and auto key on */ CSMKeyControll( &F2203->OPN, &(F2203->CH[2]) ); } } return F2203->OPN.ST.irq; } void ym2203_set_mutemask(void *chip, UINT32 MuteMask) { YM2203 *F2203 = (YM2203 *)chip; UINT8 CurChn; for (CurChn = 0; CurChn < 3; CurChn ++) F2203->CH[CurChn].Muted = (MuteMask >> CurChn) & 0x01; return; } #endif /* BUILD_YM2203 */ #if (BUILD_YM2608||BUILD_YM2610||BUILD_YM2610B) /* ADPCM type A channel struct */ typedef struct { UINT8 flag; /* port state */ UINT8 flagMask; /* arrived flag mask */ UINT8 now_data; /* current ROM data */ UINT32 now_addr; /* current ROM address */ UINT32 now_step; UINT32 step; UINT32 start; /* sample data start address*/ UINT32 end; /* sample data end address */ UINT8 IL; /* Instrument Level */ INT32 adpcm_acc; /* accumulator */ INT32 adpcm_step; /* step */ INT32 adpcm_out; /* (speedup) hiro-shi!! */ INT8 vol_mul; /* volume in "0.75dB" steps */ UINT8 vol_shift; /* volume in "-6dB" steps */ INT32 *pan; /* &out_adpcm[OPN_xxxx] */ UINT8 Muted; } ADPCM_CH; /* here's the virtual YM2610 */ typedef struct { // DEV_DATA _devData; UINT8 REGS[512]; /* registers */ FM_OPN OPN; /* OPN state */ FM_CH CH[6]; /* channel state */ UINT8 addr_A1; /* address line A1 */ /* ADPCM-A unit */ UINT8 *pcmbuf; /* pcm rom buffer */ UINT32 pcm_size; /* size of pcm rom */ UINT8 adpcmTL; /* adpcmA total level */ ADPCM_CH adpcm[6]; /* adpcm channels */ UINT32 adpcmreg[0x30]; /* registers */ UINT8 adpcm_arrivedEndAddress; YM_DELTAT deltaT; /* Delta-T ADPCM unit */ UINT8 MuteDeltaT; UINT8 flagmask; /* YM2608 only */ UINT8 irqmask; /* YM2608 only */ } YM2610; /* here is the virtual YM2608 */ typedef YM2610 YM2608; /**** YM2610 ADPCM defines ****/ #define ADPCM_SHIFT (16) /* frequency step rate */ #define ADPCMA_ADDRESS_SHIFT 8 /* adpcm A address shift */ /* Algorithm and tables verified on real YM2608 and YM2610 */ /* usual ADPCM table (16 * 1.1^N) */ static const int 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 }; /* different from the usual ADPCM table */ static const int step_inc[8] = { -1*16, -1*16, -1*16, -1*16, 2*16, 5*16, 7*16, 9*16 }; /* speedup purposes only */ static int jedi_table[ 49*16 ]; static void Init_ADPCMATable(void) { int step, nib; for (step = 0; step < 49; step++) { /* loop over all nibbles and compute the difference */ for (nib = 0; nib < 16; nib++) { int value = (2*(nib & 0x07) + 1) * steps[step] / 8; jedi_table[step*16 + nib] = (nib&0x08) ? -value : value; } } } /* ADPCM A (Non control type) : calculate one channel output */ INLINE void ADPCMA_calc_chan( YM2610 *F2610, ADPCM_CH *ch ) { UINT32 step; UINT8 data; if (ch->Muted) return; ch->now_step += ch->step; if ( ch->now_step >= (1<now_step >> ADPCM_SHIFT; ch->now_step &= (1< instead of == */ /* YM2610 checks lower 20 bits only, the 4 MSB bits are sample bank */ /* Here we use 1<<21 to compensate for nibble calculations */ if ( (ch->now_addr & ((1<<21)-1)) == ((ch->end<<1) & ((1<<21)-1)) ) { ch->flag = 0; F2610->adpcm_arrivedEndAddress |= ch->flagMask; return; } #if 0 if ( ch->now_addr > (F2610->pcmsizeA<<1) ) { LOG(LOG_WAR,("YM2610: Attempting to play past adpcm rom size!\n" )); return; } #endif if ( ch->now_addr&1 ) data = ch->now_data & 0x0f; else { ch->now_data = *(F2610->pcmbuf+(ch->now_addr>>1)); data = (ch->now_data >> 4) & 0x0f; } ch->now_addr++; ch->adpcm_acc += jedi_table[ch->adpcm_step + data]; /* the 12-bit accumulator wraps on the ym2610 and ym2608 (like the msm5205), it does not saturate (like the msm5218) */ ch->adpcm_acc &= 0xfff; /* extend 12-bit signed int */ if (ch->adpcm_acc & 0x800) ch->adpcm_acc |= ~0xfff; ch->adpcm_step += step_inc[data & 7]; Limit( ch->adpcm_step, 48*16, 0*16 ); }while(--step); /* calc pcm * volume data */ ch->adpcm_out = ((ch->adpcm_acc * ch->vol_mul) >> ch->vol_shift) & ~3; /* multiply, shift and mask out 2 LSB bits */ } /* output for work of output channels (out_adpcm[OPNxxxx])*/ *(ch->pan) += ch->adpcm_out; } /* ADPCM type A Write */ static void FM_ADPCMAWrite(YM2610 *F2610,int r,int v) { ADPCM_CH *adpcm = F2610->adpcm; UINT8 c = r&0x07; F2610->adpcmreg[r] = v&0xff; /* stock data */ switch( r ) { case 0x00: /* DM,--,C5,C4,C3,C2,C1,C0 */ if( !(v&0x80) ) { /* KEY ON */ for( c = 0; c < 6; c++ ) { if( (v>>c)&1 ) { /**** start adpcm ****/ // The .step variable is already set and for the YM2608 it is different on channels 4 and 5. //adpcm[c].step = (UINT32)((float)(1<OPN.ST.freqbase)/3.0); adpcm[c].now_addr = adpcm[c].start<<1; adpcm[c].now_step = 0; adpcm[c].adpcm_acc = 0; adpcm[c].adpcm_step= 0; adpcm[c].adpcm_out = 0; adpcm[c].flag = 1; if(F2610->pcmbuf==NULL) { /* Check ROM Mapped */ //logerror("YM2608-YM2610: ADPCM-A rom not mapped\n"); adpcm[c].flag = 0; } else { if(adpcm[c].end >= F2610->pcm_size) { /* Check End in Range */ //logerror("YM2610: ADPCM-A end out of range: $%08x\n",adpcm[c].end); /*adpcm[c].end = F2610->pcm_size-1;*/ /* JB: DO NOT uncomment this, otherwise you will break the comparison in the ADPCM_CALC_CHA() */ } if(adpcm[c].start >= F2610->pcm_size) /* Check Start in Range */ { //logerror("YM2608-YM2610: ADPCM-A start out of range: $%08x\n",adpcm[c].start); adpcm[c].flag = 0; } } } } } else { /* KEY OFF */ for( c = 0; c < 6; c++ ) if( (v>>c)&1 ) adpcm[c].flag = 0; } break; case 0x01: /* B0-5 = TL */ F2610->adpcmTL = (v & 0x3f) ^ 0x3f; for( c = 0; c < 6; c++ ) { int volume = F2610->adpcmTL + adpcm[c].IL; if ( volume >= 63 ) /* This is correct, 63 = quiet */ { adpcm[c].vol_mul = 0; adpcm[c].vol_shift = 0; } else { adpcm[c].vol_mul = 15 - (volume & 7); /* so called 0.75 dB */ adpcm[c].vol_shift = (UINT8)(1 + (volume >> 3)); /* Yamaha engineers used the approximation: each -6 dB is close to divide by two (shift right) */ } /* calc pcm * volume data */ adpcm[c].adpcm_out = ((adpcm[c].adpcm_acc * adpcm[c].vol_mul) >> adpcm[c].vol_shift) & ~3; /* multiply, shift and mask out low 2 bits */ } break; default: c = r&0x07; if( c >= 0x06 ) return; switch( r&0x38 ) { case 0x08: /* B7=L,B6=R, B4-0=IL */ { int volume; adpcm[c].IL = (v & 0x1f) ^ 0x1f; volume = F2610->adpcmTL + adpcm[c].IL; if ( volume >= 63 ) /* This is correct, 63 = quiet */ { adpcm[c].vol_mul = 0; adpcm[c].vol_shift = 0; } else { adpcm[c].vol_mul = 15 - (volume & 7); /* so called 0.75 dB */ adpcm[c].vol_shift = (UINT8)(1 + (volume >> 3)); /* Yamaha engineers used the approximation: each -6 dB is close to divide by two (shift right) */ } adpcm[c].pan = &F2610->OPN.out_adpcm[(v>>6)&0x03]; /* calc pcm * volume data */ adpcm[c].adpcm_out = ((adpcm[c].adpcm_acc * adpcm[c].vol_mul) >> adpcm[c].vol_shift) & ~3; /* multiply, shift and mask out low 2 bits */ } break; case 0x10: case 0x18: adpcm[c].start = ( (F2610->adpcmreg[0x18 + c]*0x0100 | F2610->adpcmreg[0x10 + c]) << ADPCMA_ADDRESS_SHIFT); break; case 0x20: case 0x28: adpcm[c].end = ( (F2610->adpcmreg[0x28 + c]*0x0100 | F2610->adpcmreg[0x20 + c]) << ADPCMA_ADDRESS_SHIFT); adpcm[c].end += (1<ST, 0xf7); /* don't touch BUFRDY flag otherwise we'd have to call ymdeltat module to set the flag back */ } else { /* Set status flag mask */ F2608->flagmask = (~(v&0x1f)); FM_IRQMASK_SET(&OPN->ST, (F2608->irqmask & F2608->flagmask) ); } } /* compatible mode & IRQ enable control 0x29 */ INLINE void YM2608IRQMaskWrite(FM_OPN *OPN, YM2608 *F2608, int v) { /* SCH,xx,xxx,EN_ZERO,EN_BRDY,EN_EOS,EN_TB,EN_TA */ /* extend 3ch. enable/disable */ if(v&0x80) OPN->type |= TYPE_6CH; /* OPNA mode - 6 FM channels */ else OPN->type &= ~TYPE_6CH; /* OPN mode - 3 FM channels */ /* IRQ MASK store and set */ F2608->irqmask = v&0x1f; FM_IRQMASK_SET(&OPN->ST, (F2608->irqmask & F2608->flagmask) ); } /* Generate samples for one of the YM2608s */ void ym2608_update_one(void *chip, UINT32 length, stream_sample_t **buffer) { YM2608 *F2608 = (YM2608 *)chip; FM_OPN *OPN = &F2608->OPN; YM_DELTAT *DELTAT = &F2608->deltaT; UINT32 i; UINT8 j; stream_sample_t *bufL,*bufR; FM_CH *cch[6]; INT32 *out_fm = OPN->out_fm; /* set bufer */ if (buffer != NULL) { bufL = buffer[0]; bufR = buffer[1]; } else { bufL = bufR = NULL; } cch[0] = &F2608->CH[0]; cch[1] = &F2608->CH[1]; cch[2] = &F2608->CH[2]; cch[3] = &F2608->CH[3]; cch[4] = &F2608->CH[4]; cch[5] = &F2608->CH[5]; /* refresh PG and EG */ refresh_fc_eg_chan( OPN, cch[0] ); refresh_fc_eg_chan( OPN, cch[1] ); if( (OPN->ST.mode & 0xc0) ) { /* 3SLOT MODE */ if( cch[2]->SLOT[SLOT1].Incr==-1) { refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT1] , OPN->SL3.fc[1] , OPN->SL3.kcode[1] ); refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT2] , OPN->SL3.fc[2] , OPN->SL3.kcode[2] ); refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT3] , OPN->SL3.fc[0] , OPN->SL3.kcode[0] ); refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT4] , cch[2]->fc , cch[2]->kcode ); } } else refresh_fc_eg_chan( OPN, cch[2] ); refresh_fc_eg_chan( OPN, cch[3] ); refresh_fc_eg_chan( OPN, cch[4] ); refresh_fc_eg_chan( OPN, cch[5] ); if (! length) { update_ssg_eg_channel(&cch[0]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[1]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[2]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[3]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[4]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[5]->SLOT[SLOT1]); } /* buffering */ for(i=0; i < length ; i++) { /* clear output acc. */ OPN->out_adpcm[OUTD_LEFT] = OPN->out_adpcm[OUTD_RIGHT] = OPN->out_adpcm[OUTD_CENTER] = 0; OPN->out_delta[OUTD_LEFT] = OPN->out_delta[OUTD_RIGHT] = OPN->out_delta[OUTD_CENTER] = 0; /* clear outputs */ out_fm[0] = 0; out_fm[1] = 0; out_fm[2] = 0; out_fm[3] = 0; out_fm[4] = 0; out_fm[5] = 0; /* update SSG-EG output */ update_ssg_eg_channel(&cch[0]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[1]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[2]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[3]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[4]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[5]->SLOT[SLOT1]); /* calculate FM */ chan_calc(OPN, cch[0], 0 ); chan_calc(OPN, cch[1], 1 ); chan_calc(OPN, cch[2], 2 ); chan_calc(OPN, cch[3], 3 ); chan_calc(OPN, cch[4], 4 ); chan_calc(OPN, cch[5], 5 ); /* deltaT ADPCM */ if( DELTAT->portstate&0x80 && ! F2608->MuteDeltaT ) YM_DELTAT_ADPCM_CALC(DELTAT); /* ADPCMA */ for( j = 0; j < 6; j++ ) { if( F2608->adpcm[j].flag ) ADPCMA_calc_chan( F2608, &F2608->adpcm[j]); } /* advance LFO */ advance_lfo(OPN); /* advance envelope generator */ OPN->eg_timer += OPN->eg_timer_add; while (OPN->eg_timer >= OPN->eg_timer_overflow) { OPN->eg_timer -= OPN->eg_timer_overflow; OPN->eg_cnt++; advance_eg_channel(OPN, &cch[0]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[1]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[2]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[3]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[4]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[5]->SLOT[SLOT1]); } /* buffering */ { stream_sample_t lt,rt; lt = (OPN->out_adpcm[OUTD_LEFT] + OPN->out_adpcm[OUTD_CENTER])<<1; rt = (OPN->out_adpcm[OUTD_RIGHT] + OPN->out_adpcm[OUTD_CENTER])<<1; lt += (OPN->out_delta[OUTD_LEFT] + OPN->out_delta[OUTD_CENTER])>>8; rt += (OPN->out_delta[OUTD_RIGHT] + OPN->out_delta[OUTD_CENTER])>>8; lt += (out_fm[0] & OPN->pan[0]); rt += (out_fm[0] & OPN->pan[1]); lt += (out_fm[1] & OPN->pan[2]); rt += (out_fm[1] & OPN->pan[3]); lt += (out_fm[2] & OPN->pan[4]); rt += (out_fm[2] & OPN->pan[5]); lt += (out_fm[3] & OPN->pan[6]); rt += (out_fm[3] & OPN->pan[7]); lt += (out_fm[4] & OPN->pan[8]); rt += (out_fm[4] & OPN->pan[9]); lt += (out_fm[5] & OPN->pan[10]); rt += (out_fm[5] & OPN->pan[11]); // I like that emulation can have more precision than the real thing. //lt >>= 1; // shift right verified on real YM2608 //rt >>= 1; /* buffering */ bufL[i] = lt; bufR[i] = rt; } /* CSM mode: if CSM Key ON has occured, CSM Key OFF need to be sent */ /* only if Timer A does not overflow again (i.e CSM Key ON not set again) */ OPN->SL3.key_csm <<= 1; /* timer A control */ INTERNAL_TIMER_A( &OPN->ST , cch[2] ) /* CSM Mode Key ON still disabled */ if (OPN->SL3.key_csm & 2) { /* CSM Mode Key OFF (verified by Nemesis on real hardware) */ FM_KEYOFF_CSM(cch[2],SLOT1); FM_KEYOFF_CSM(cch[2],SLOT2); FM_KEYOFF_CSM(cch[2],SLOT3); FM_KEYOFF_CSM(cch[2],SLOT4); OPN->SL3.key_csm = 0; } } /* timer B control */ INTERNAL_TIMER_B(&OPN->ST,length) /* check IRQ for DELTA-T EOS */ FM_STATUS_SET(&OPN->ST, 0); } static void ym2608_update_req(void *param) { ym2608_update_one(param, 0, NULL); } static void YM2608_deltat_status_set(void *chip, UINT8 changebits) { YM2608 *F2608 = (YM2608 *)chip; FM_STATUS_SET(&(F2608->OPN.ST), changebits); } static void YM2608_deltat_status_reset(void *chip, UINT8 changebits) { YM2608 *F2608 = (YM2608 *)chip; FM_STATUS_RESET(&(F2608->OPN.ST), changebits); } /* YM2608(OPNA) */ void * ym2608_init(void *param, UINT32 clock, UINT32 rate, FM_TIMERHANDLER timer_handler,FM_IRQHANDLER IRQHandler) { YM2608 *F2608; /* allocate extend state space */ F2608 = (YM2608 *)calloc(1,sizeof(YM2608)); if( F2608==NULL) return NULL; /* allocate total level table (128kb space) */ init_tables(); F2608->OPN.ST.param = param; F2608->OPN.type = TYPE_YM2608; F2608->OPN.P_CH = F2608->CH; F2608->OPN.ST.clock = clock; F2608->OPN.ST.rate = rate; OPNCheckNativeSampleRate(&F2608->OPN); /* External handlers */ F2608->OPN.ST.timer_handler = timer_handler; F2608->OPN.ST.IRQ_Handler = IRQHandler; OPNLinkSSG(&F2608->OPN, NULL, NULL); OPNSetSmplRateChgCallback(&F2608->OPN, NULL, NULL); /* DELTA-T */ F2608->deltaT.memory = NULL; F2608->deltaT.memory_size = 0x00; F2608->deltaT.memory_mask = 0x00; //F2608->deltaT.write_time = 20.0 / clock; /* a single byte write takes 20 cycles of main clock */ //F2608->deltaT.read_time = 18.0 / clock; /* a single byte read takes 18 cycles of main clock */ F2608->deltaT.status_set_handler = YM2608_deltat_status_set; F2608->deltaT.status_reset_handler = YM2608_deltat_status_reset; F2608->deltaT.status_change_which_chip = F2608; F2608->deltaT.status_change_EOS_bit = 0x04; /* status flag: set bit2 on End Of Sample */ F2608->deltaT.status_change_BRDY_bit = 0x08; /* status flag: set bit3 on BRDY */ F2608->deltaT.status_change_ZERO_bit = 0x10; /* status flag: set bit4 if silence continues for more than 290 miliseconds while recording the ADPCM */ YM_DELTAT_ADPCM_Init(&F2608->deltaT,YM_DELTAT_EMULATION_MODE_NORMAL,5,F2608->OPN.out_delta,1<<23); /* ADPCM Rhythm */ F2608->pcmbuf = (UINT8*)YM2608_ADPCM_ROM; F2608->pcm_size = 0x2000; Init_ADPCMATable(); ym2608_set_mutemask(F2608, 0x00); return F2608; } /* link SSG emulator */ void ym2608_link_ssg(void *chip, const ssg_callbacks *ssg, void *ssg_param) { YM2608 *F2608 = (YM2608 *)chip; OPNLinkSSG(&F2608->OPN, ssg, ssg_param); OPNPrescaler_w(&F2608->OPN, 1, 2); return; } /* set sample rate change callback */ /*void ym2608_set_srchg_cb(void *chip, DEVCB_SRATE_CHG cbFunc, void* dataPtr) { YM2608 *F2608 = (YM2608 *)chip; OPNSetSmplRateChgCallback(&F2608->OPN, cbFunc, dataPtr); return; }*/ /* shut down emulator */ void ym2608_shutdown(void *chip) { YM2608 *F2608 = (YM2608 *)chip; free(F2608->deltaT.memory); F2608->deltaT.memory = NULL; free(F2608); } /* reset one of chips */ void ym2608_reset_chip(void *chip) { int i; YM2608 *F2608 = (YM2608 *)chip; FM_OPN *OPN = &F2608->OPN; YM_DELTAT *DELTAT = &F2608->deltaT; /* Reset Prescaler */ OPNPrescaler_w(OPN , 0 , 2); F2608->deltaT.freqbase = OPN->ST.freqbase; /* reset SSG section */ OPN->ST.SSG_funcs.reset(OPN->ST.SSG_param); /* status clear */ FM_BUSY_CLEAR(&OPN->ST); /* register 0x29 - default value after reset is: enable only 3 FM channels and enable all the status flags */ YM2608IRQMaskWrite(OPN, F2608, 0x1f ); /* default value for D4-D0 is 1 */ /* register 0x10, A1=1 - default value is 1 for D4, D3, D2, 0 for the rest */ YM2608IRQFlagWrite(OPN, F2608, 0x1c ); /* default: enable timer A and B, disable EOS, BRDY and ZERO */ OPN->eg_timer = 0; OPN->eg_cnt = 0; OPN->ST.TAC = 0; OPN->ST.TBC = 0; OPN->SL3.key_csm = 0; OPN->ST.status = 0; OPN->ST.mode = 0; OPN->ST.irq = 0; memset(F2608->REGS, 0x00, sizeof(UINT8) * 512); reset_channels( &OPN->ST , F2608->CH , 6 ); /* reset Operator paramater */ for(i = 0xb6 ; i >= 0xb4 ; i-- ) { OPNWriteReg(OPN,i ,0xc0); OPNWriteReg(OPN,i|0x100,0xc0); } for(i = 0xb2 ; i >= 0x30 ; i-- ) { OPNWriteReg(OPN,i ,0); OPNWriteReg(OPN,i|0x100,0); } OPNWriteMode(OPN,0x27,0x30); /* mode 0 , timer reset */ for(i = 0x26 ; i >= 0x20 ; i-- ) OPNWriteReg(OPN,i,0); /* ADPCM - percussion sounds */ for( i = 0; i < 6; i++ ) { if (i<=3) /* channels 0,1,2,3 */ F2608->adpcm[i].step = (UINT32)((float)(1<OPN.ST.freqbase)/3.0); else /* channels 4 and 5 work with slower clock */ F2608->adpcm[i].step = (UINT32)((float)(1<OPN.ST.freqbase)/6.0); F2608->adpcm[i].start = YM2608_ADPCM_ROM_addr[i*2]; F2608->adpcm[i].end = YM2608_ADPCM_ROM_addr[i*2+1]; F2608->adpcm[i].now_addr = 0; F2608->adpcm[i].now_step = 0; // F2608->adpcm[i].delta = 21866; F2608->adpcm[i].vol_mul = 0; F2608->adpcm[i].pan = &OPN->out_adpcm[OUTD_CENTER]; /* default center */ F2608->adpcm[i].flagMask = 0; F2608->adpcm[i].flag = 0; F2608->adpcm[i].adpcm_acc = 0; F2608->adpcm[i].adpcm_step= 0; F2608->adpcm[i].adpcm_out = 0; } F2608->adpcmTL = 0x3f; F2608->adpcm_arrivedEndAddress = 0; /* not used */ /* DELTA-T unit */ DELTAT->freqbase = OPN->ST.freqbase; YM_DELTAT_ADPCM_Reset(DELTAT,OUTD_CENTER); } /* YM2608 write */ /* n = number */ /* a = address */ /* v = value */ void ym2608_write(void *chip, UINT8 a,UINT8 v) { YM2608 *F2608 = (YM2608 *)chip; FM_OPN *OPN = &F2608->OPN; int addr; //v &= 0xff; /*adjust to 8 bit bus */ switch(a&3) { case 0: /* address port 0 */ OPN->ST.address = v; F2608->addr_A1 = 0; /* Write register to SSG emulator */ if( v < 16 ) OPN->ST.SSG_funcs.write(OPN->ST.SSG_param,a,v); /* prescaler selecter : 2d,2e,2f */ if( v >= 0x2d && v <= 0x2f ) { OPNPrescaler_w(OPN , v , 2); //TODO: set ADPCM[c].step F2608->deltaT.freqbase = OPN->ST.freqbase; } break; case 1: /* data port 0 */ if (F2608->addr_A1 != 0) break; /* verified on real YM2608 */ addr = OPN->ST.address; F2608->REGS[addr] = v; switch(addr & 0xf0) { case 0x00: /* SSG section */ /* Write data to SSG emulator */ OPN->ST.SSG_funcs.write(OPN->ST.SSG_param,a,v); break; case 0x10: /* 0x10-0x1f : Rhythm section */ ym2608_update_req(F2608); FM_ADPCMAWrite(F2608,addr-0x10,v); break; case 0x20: /* Mode Register */ switch(addr) { case 0x29: /* SCH,xx,xxx,EN_ZERO,EN_BRDY,EN_EOS,EN_TB,EN_TA */ YM2608IRQMaskWrite(OPN, F2608, v); break; default: ym2608_update_req(F2608); OPNWriteMode(OPN,addr,v); } break; default: /* OPN section */ ym2608_update_req(F2608); OPNWriteReg(OPN,addr,v); } break; case 2: /* address port 1 */ OPN->ST.address = v; F2608->addr_A1 = 1; break; case 3: /* data port 1 */ if (F2608->addr_A1 != 1) break; /* verified on real YM2608 */ addr = OPN->ST.address; F2608->REGS[addr | 0x100] = v; ym2608_update_req(F2608); switch( addr & 0xf0 ) { case 0x00: /* DELTAT PORT */ switch( addr ) { case 0x0e: /* DAC data */ //logerror("YM2608: write to DAC data (unimplemented) value=%02x\n",v); break; default: /* 0x00-0x0d */ YM_DELTAT_ADPCM_Write(&F2608->deltaT,addr,v); } break; case 0x10: /* IRQ Flag control */ if( addr == 0x10 ) { YM2608IRQFlagWrite(OPN, F2608, v); } break; default: OPNWriteReg(OPN,addr | 0x100,v); } } //return OPN->ST.irq; return; } UINT8 ym2608_read(void *chip,UINT8 a) { YM2608 *F2608 = (YM2608 *)chip; UINT8 addr = F2608->OPN.ST.address; UINT8 ret = 0; switch( a&3 ) { case 0: /* status 0 : YM2203 compatible */ /* BUSY:x:x:x:x:x:FLAGB:FLAGA */ ret = FM_STATUS_FLAG(&F2608->OPN.ST) & 0x83; break; case 1: /* status 0, ID */ if( addr < 16 ) ret = F2608->OPN.ST.SSG_funcs.read(F2608->OPN.ST.SSG_param); else if(addr == 0xff) ret = 0x01; /* ID code */ break; case 2: /* status 1 : status 0 + ADPCM status */ /* BUSY : x : PCMBUSY : ZERO : BRDY : EOS : FLAGB : FLAGA */ ret = (FM_STATUS_FLAG(&F2608->OPN.ST) & (F2608->flagmask|0x80)) | ((F2608->deltaT.PCM_BSY & 1)<<5) ; break; case 3: if(addr == 0x08) { ret = YM_DELTAT_ADPCM_Read(&F2608->deltaT); } else if(addr == 0x0f) { //logerror("YM2608 A/D conversion is accessed but not implemented !\n"); ret = 0x80; /* 2's complement PCM data - result from A/D conversion */ } break; } return ret; } UINT8 ym2608_timer_over(void *chip,UINT8 c) { YM2608 *F2608 = (YM2608 *)chip; switch(c) { #if 0 case 2: { /* BUFRDY flag */ YM_DELTAT_BRDY_callback( &F2608->deltaT ); } break; #endif case 1: { /* Timer B */ TimerBOver( &(F2608->OPN.ST) ); } break; case 0: { /* Timer A */ ym2608_update_req(F2608); /* timer update */ TimerAOver( &(F2608->OPN.ST) ); /* CSM mode key,TL controll */ if ((F2608->OPN.ST.mode & 0xc0) == 0x80) { /* CSM mode total level latch and auto key on */ CSMKeyControll( &F2608->OPN, &(F2608->CH[2]) ); } } break; default: break; } return F2608->OPN.ST.irq; } void ym2608_alloc_pcmromb(void* chip, UINT32 memsize) { YM2608* F2608 = (YM2608*)chip; if (F2608->deltaT.memory_size == memsize) return; F2608->deltaT.memory = (UINT8*)realloc(F2608->deltaT.memory, memsize); F2608->deltaT.memory_size = memsize; memset(F2608->deltaT.memory, 0, memsize); YM_DELTAT_calc_mem_mask(&F2608->deltaT); return; } void ym2608_write_pcmromb(void* chip, UINT32 offset, UINT32 length, const UINT8* data) { YM2608* F2608 = (YM2608*)chip; if (offset > F2608->deltaT.memory_size) return; if (offset + length > F2608->deltaT.memory_size) length = F2608->deltaT.memory_size - offset; memcpy(F2608->deltaT.memory + offset, data, length); return; } void ym2608_set_mutemask(void *chip, UINT32 MuteMask) { YM2608 *F2608 = (YM2608 *)chip; UINT8 CurChn; for (CurChn = 0; CurChn < 6; CurChn ++) F2608->CH[CurChn].Muted = (MuteMask >> CurChn) & 0x01; for (CurChn = 0; CurChn < 6; CurChn ++) F2608->adpcm[CurChn].Muted = (MuteMask >> (CurChn + 6)) & 0x01; F2608->MuteDeltaT = (MuteMask >> 12) & 0x01; return; } #endif /* BUILD_YM2608 */ #if (BUILD_YM2610||BUILD_YM2610B) /* YM2610(OPNB) */ /* Generate samples for one of the YM2610s */ void ym2610_update_one(void *chip, UINT32 length, DEV_SMPL **buffer) { YM2610 *F2610 = (YM2610 *)chip; FM_OPN *OPN = &F2610->OPN; YM_DELTAT *DELTAT = &F2610->deltaT; UINT32 i; UINT8 j; DEV_SMPL *bufL,*bufR; FM_CH *cch[4]; INT32 *out_fm = OPN->out_fm; /* buffer setup */ if (buffer != NULL) { bufL = buffer[0]; bufR = buffer[1]; } else { bufL = bufR = NULL; } cch[0] = &F2610->CH[1]; cch[1] = &F2610->CH[2]; cch[2] = &F2610->CH[4]; cch[3] = &F2610->CH[5]; #ifdef YM2610B_WARNING #define FM_KEY_IS(SLOT) ((SLOT)->key) #define FM_MSG_YM2610B "YM2610-%p.CH%d is playing,Check whether the type of the chip is YM2610B\n" /* Check YM2610B warning message */ if( FM_KEY_IS(&F2610->CH[0].SLOT[3]) ) { LOG(LOG_WAR,(FM_MSG_YM2610B,F2610->OPN.ST.param,0)); FM_KEY_IS(&F2610->CH[0].SLOT[3]) = 0; } if( FM_KEY_IS(&F2610->CH[3].SLOT[3]) ) { LOG(LOG_WAR,(FM_MSG_YM2610B,F2610->OPN.ST.param,3)); FM_KEY_IS(&F2610->CH[3].SLOT[3]) = 0; } #endif /* refresh PG and EG */ refresh_fc_eg_chan( OPN, cch[0] ); if( (OPN->ST.mode & 0xc0) ) { /* 3SLOT MODE */ if( cch[1]->SLOT[SLOT1].Incr==-1) { refresh_fc_eg_slot(OPN, &cch[1]->SLOT[SLOT1] , OPN->SL3.fc[1] , OPN->SL3.kcode[1] ); refresh_fc_eg_slot(OPN, &cch[1]->SLOT[SLOT2] , OPN->SL3.fc[2] , OPN->SL3.kcode[2] ); refresh_fc_eg_slot(OPN, &cch[1]->SLOT[SLOT3] , OPN->SL3.fc[0] , OPN->SL3.kcode[0] ); refresh_fc_eg_slot(OPN, &cch[1]->SLOT[SLOT4] , cch[1]->fc , cch[1]->kcode ); } } else refresh_fc_eg_chan( OPN, cch[1] ); refresh_fc_eg_chan( OPN, cch[2] ); refresh_fc_eg_chan( OPN, cch[3] ); if (! length) { update_ssg_eg_channel(&cch[0]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[1]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[2]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[3]->SLOT[SLOT1]); } /* buffering */ for(i=0; i < length ; i++) { /* clear output acc. */ OPN->out_adpcm[OUTD_LEFT] = OPN->out_adpcm[OUTD_RIGHT] = OPN->out_adpcm[OUTD_CENTER] = 0; OPN->out_delta[OUTD_LEFT] = OPN->out_delta[OUTD_RIGHT] = OPN->out_delta[OUTD_CENTER] = 0; /* clear outputs */ out_fm[1] = 0; out_fm[2] = 0; out_fm[4] = 0; out_fm[5] = 0; /* update SSG-EG output */ update_ssg_eg_channel(&cch[0]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[1]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[2]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[3]->SLOT[SLOT1]); /* calculate FM */ chan_calc(OPN, cch[0], 1 ); /*remapped to 1*/ chan_calc(OPN, cch[1], 2 ); /*remapped to 2*/ chan_calc(OPN, cch[2], 4 ); /*remapped to 4*/ chan_calc(OPN, cch[3], 5 ); /*remapped to 5*/ /* deltaT ADPCM */ if( DELTAT->portstate&0x80 && ! F2610->MuteDeltaT ) YM_DELTAT_ADPCM_CALC(DELTAT); /* ADPCMA */ for( j = 0; j < 6; j++ ) { if( F2610->adpcm[j].flag ) ADPCMA_calc_chan( F2610, &F2610->adpcm[j]); } /* advance LFO */ advance_lfo(OPN); /* advance envelope generator */ OPN->eg_timer += OPN->eg_timer_add; while (OPN->eg_timer >= OPN->eg_timer_overflow) { OPN->eg_timer -= OPN->eg_timer_overflow; OPN->eg_cnt++; advance_eg_channel(OPN, &cch[0]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[1]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[2]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[3]->SLOT[SLOT1]); } /* buffering */ { DEV_SMPL lt,rt; lt = (OPN->out_adpcm[OUTD_LEFT] + OPN->out_adpcm[OUTD_CENTER])<<1; rt = (OPN->out_adpcm[OUTD_RIGHT] + OPN->out_adpcm[OUTD_CENTER])<<1; lt += (OPN->out_delta[OUTD_LEFT] + OPN->out_delta[OUTD_CENTER])>>8; rt += (OPN->out_delta[OUTD_RIGHT] + OPN->out_delta[OUTD_CENTER])>>8; lt += (out_fm[1] & OPN->pan[2]); rt += (out_fm[1] & OPN->pan[3]); lt += (out_fm[2] & OPN->pan[4]); rt += (out_fm[2] & OPN->pan[5]); lt += (out_fm[4] & OPN->pan[8]); rt += (out_fm[4] & OPN->pan[9]); lt += (out_fm[5] & OPN->pan[10]); rt += (out_fm[5] & OPN->pan[11]); //lt >>= 1; // the shift right was verified on real chip //rt >>= 1; /* buffering */ bufL[i] = lt; bufR[i] = rt; } /* CSM mode: if CSM Key ON has occured, CSM Key OFF need to be sent */ /* only if Timer A does not overflow again (i.e CSM Key ON not set again) */ OPN->SL3.key_csm <<= 1; /* timer A control */ INTERNAL_TIMER_A( &OPN->ST , cch[1] ) /* CSM Mode Key ON still disabled */ if (OPN->SL3.key_csm & 2) { /* CSM Mode Key OFF (verified by Nemesis on real hardware) */ FM_KEYOFF_CSM(cch[1],SLOT1); FM_KEYOFF_CSM(cch[1],SLOT2); FM_KEYOFF_CSM(cch[1],SLOT3); FM_KEYOFF_CSM(cch[1],SLOT4); OPN->SL3.key_csm = 0; } } /* timer B control */ INTERNAL_TIMER_B(&OPN->ST,length) } static void ym2610_update_req(void *param) { ym2610b_update_one(param, 0, NULL); } #if BUILD_YM2610B /* Generate samples for one of the YM2610Bs */ void ym2610b_update_one(void *chip, UINT32 length, DEV_SMPL **buffer) { YM2610 *F2610 = (YM2610 *)chip; FM_OPN *OPN = &F2610->OPN; YM_DELTAT *DELTAT = &F2610->deltaT; UINT32 i; UINT8 j; DEV_SMPL *bufL,*bufR; FM_CH *cch[6]; INT32 *out_fm = OPN->out_fm; /* buffer setup */ if (buffer != NULL) { bufL = buffer[0]; bufR = buffer[1]; } else { bufL = bufR = NULL; } cch[0] = &F2610->CH[0]; cch[1] = &F2610->CH[1]; cch[2] = &F2610->CH[2]; cch[3] = &F2610->CH[3]; cch[4] = &F2610->CH[4]; cch[5] = &F2610->CH[5]; /* refresh PG and EG */ refresh_fc_eg_chan( OPN, cch[0] ); refresh_fc_eg_chan( OPN, cch[1] ); if( (OPN->ST.mode & 0xc0) ) { /* 3SLOT MODE */ if( cch[2]->SLOT[SLOT1].Incr==-1) { refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT1] , OPN->SL3.fc[1] , OPN->SL3.kcode[1] ); refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT2] , OPN->SL3.fc[2] , OPN->SL3.kcode[2] ); refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT3] , OPN->SL3.fc[0] , OPN->SL3.kcode[0] ); refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT4] , cch[2]->fc , cch[2]->kcode ); } } else refresh_fc_eg_chan( OPN, cch[2] ); refresh_fc_eg_chan( OPN, cch[3] ); refresh_fc_eg_chan( OPN, cch[4] ); refresh_fc_eg_chan( OPN, cch[5] ); if (! length) { update_ssg_eg_channel(&cch[0]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[1]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[2]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[3]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[4]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[5]->SLOT[SLOT1]); } /* buffering */ for(i=0; i < length ; i++) { /* clear output acc. */ OPN->out_adpcm[OUTD_LEFT] = OPN->out_adpcm[OUTD_RIGHT] = OPN->out_adpcm[OUTD_CENTER] = 0; OPN->out_delta[OUTD_LEFT] = OPN->out_delta[OUTD_RIGHT] = OPN->out_delta[OUTD_CENTER] = 0; /* clear outputs */ out_fm[0] = 0; out_fm[1] = 0; out_fm[2] = 0; out_fm[3] = 0; out_fm[4] = 0; out_fm[5] = 0; /* update SSG-EG output */ update_ssg_eg_channel(&cch[0]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[1]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[2]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[3]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[4]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[5]->SLOT[SLOT1]); /* calculate FM */ chan_calc(OPN, cch[0], 0 ); chan_calc(OPN, cch[1], 1 ); chan_calc(OPN, cch[2], 2 ); chan_calc(OPN, cch[3], 3 ); chan_calc(OPN, cch[4], 4 ); chan_calc(OPN, cch[5], 5 ); /* deltaT ADPCM */ if( DELTAT->portstate&0x80 && ! F2610->MuteDeltaT ) YM_DELTAT_ADPCM_CALC(DELTAT); /* ADPCMA */ for( j = 0; j < 6; j++ ) { if( F2610->adpcm[j].flag ) ADPCMA_calc_chan( F2610, &F2610->adpcm[j]); } /* advance LFO */ advance_lfo(OPN); /* advance envelope generator */ OPN->eg_timer += OPN->eg_timer_add; while (OPN->eg_timer >= OPN->eg_timer_overflow) { OPN->eg_timer -= OPN->eg_timer_overflow; OPN->eg_cnt++; advance_eg_channel(OPN, &cch[0]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[1]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[2]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[3]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[4]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[5]->SLOT[SLOT1]); } /* buffering */ { DEV_SMPL lt,rt; lt = (OPN->out_adpcm[OUTD_LEFT] + OPN->out_adpcm[OUTD_CENTER])<<1; rt = (OPN->out_adpcm[OUTD_RIGHT] + OPN->out_adpcm[OUTD_CENTER])<<1; lt += (OPN->out_delta[OUTD_LEFT] + OPN->out_delta[OUTD_CENTER])>>8; rt += (OPN->out_delta[OUTD_RIGHT] + OPN->out_delta[OUTD_CENTER])>>8; lt += (out_fm[0] & OPN->pan[0]); rt += (out_fm[0] & OPN->pan[1]); lt += (out_fm[1] & OPN->pan[2]); rt += (out_fm[1] & OPN->pan[3]); lt += (out_fm[2] & OPN->pan[4]); rt += (out_fm[2] & OPN->pan[5]); lt += (out_fm[3] & OPN->pan[6]); rt += (out_fm[3] & OPN->pan[7]); lt += (out_fm[4] & OPN->pan[8]); rt += (out_fm[4] & OPN->pan[9]); lt += (out_fm[5] & OPN->pan[10]); rt += (out_fm[5] & OPN->pan[11]); //lt >>= 1; // the shift right is verified on YM2610 //rt >>= 1; /* buffering */ bufL[i] = lt; bufR[i] = rt; } /* CSM mode: if CSM Key ON has occured, CSM Key OFF need to be sent */ /* only if Timer A does not overflow again (i.e CSM Key ON not set again) */ OPN->SL3.key_csm <<= 1; /* timer A control */ INTERNAL_TIMER_A( &OPN->ST , cch[2] ) /* CSM Mode Key ON still disabled */ if (OPN->SL3.key_csm & 2) { /* CSM Mode Key OFF (verified by Nemesis on real hardware) */ FM_KEYOFF_CSM(cch[2],SLOT1); FM_KEYOFF_CSM(cch[2],SLOT2); FM_KEYOFF_CSM(cch[2],SLOT3); FM_KEYOFF_CSM(cch[2],SLOT4); OPN->SL3.key_csm = 0; } } /* timer B control */ INTERNAL_TIMER_B(&OPN->ST,length) } #endif /* BUILD_YM2610B */ static void YM2610_deltat_status_set(void *chip, UINT8 changebits) { YM2610 *F2610 = (YM2610 *)chip; F2610->adpcm_arrivedEndAddress |= changebits; } static void YM2610_deltat_status_reset(void *chip, UINT8 changebits) { YM2610 *F2610 = (YM2610 *)chip; F2610->adpcm_arrivedEndAddress &= (~changebits); } void *ym2610_init(void *param, UINT32 clock, UINT32 rate, FM_TIMERHANDLER timer_handler,FM_IRQHANDLER IRQHandler) { YM2610 *F2610; /* allocate extend state space */ F2610 = (YM2610 *)calloc(1,sizeof(YM2610)); if( F2610==NULL) return NULL; /* allocate total level table (128kb space) */ init_tables(); /* FM */ F2610->OPN.ST.param = param; F2610->OPN.type = TYPE_YM2610; F2610->OPN.P_CH = F2610->CH; F2610->OPN.ST.clock = clock; F2610->OPN.ST.rate = rate; OPNCheckNativeSampleRate(&F2610->OPN); /* Extend handler */ F2610->OPN.ST.timer_handler = timer_handler; F2610->OPN.ST.IRQ_Handler = IRQHandler; OPNLinkSSG(&F2610->OPN, NULL, NULL); OPNSetSmplRateChgCallback(&F2610->OPN, NULL, NULL); /* ADPCM */ F2610->pcmbuf = NULL; F2610->pcm_size = 0x00; /* DELTA-T */ F2610->deltaT.memory = NULL; F2610->deltaT.memory_size = 0x00; F2610->deltaT.memory_mask = 0x00; F2610->deltaT.status_set_handler = YM2610_deltat_status_set; F2610->deltaT.status_reset_handler = YM2610_deltat_status_reset; F2610->deltaT.status_change_which_chip = F2610; F2610->deltaT.status_change_EOS_bit = 0x80; /* status flag: set bit7 on End Of Sample */ YM_DELTAT_ADPCM_Init(&F2610->deltaT,YM_DELTAT_EMULATION_MODE_YM2610,8,F2610->OPN.out_delta,1<<23); Init_ADPCMATable(); ym2610_set_mutemask(F2610, 0x00); return F2610; } /* link SSG emulator */ void ym2610_link_ssg(void *chip, const ssg_callbacks *ssg, void *ssg_param) { YM2610 *F2610 = (YM2610 *)chip; OPNLinkSSG(&F2610->OPN, ssg, ssg_param); OPNPrescaler_w(&F2610->OPN, 1, 2); return; } /* shut down emulator */ void ym2610_shutdown(void *chip) { YM2610 *F2610 = (YM2610 *)chip; free(F2610->pcmbuf); F2610->pcmbuf = NULL; free(F2610->deltaT.memory); F2610->deltaT.memory = NULL; free(F2610); } /* reset one of chip */ void ym2610_reset_chip(void *chip) { int i; YM2610 *F2610 = (YM2610 *)chip; FM_OPN *OPN = &F2610->OPN; YM_DELTAT *DELTAT = &F2610->deltaT; /* Reset Prescaler */ OPNPrescaler_w( OPN, 0, 2); /* OPN 1/6 , SSG 1/4 */ /* reset SSG section */ OPN->ST.SSG_funcs.reset(OPN->ST.SSG_param); /* status clear */ FM_IRQMASK_SET(&OPN->ST,0x03); FM_BUSY_CLEAR(&OPN->ST); OPN->eg_timer = 0; OPN->eg_cnt = 0; OPN->ST.TAC = 0; OPN->ST.TBC = 0; OPN->SL3.key_csm = 0; OPN->ST.status = 0; OPN->ST.mode = 0; OPN->ST.irq = 0; memset(F2610->REGS, 0x00, sizeof(UINT8) * 512); reset_channels( &OPN->ST , F2610->CH , 6 ); /* reset Operator paramater */ for(i = 0xb6 ; i >= 0xb4 ; i-- ) { OPNWriteReg(OPN,i ,0xc0); OPNWriteReg(OPN,i|0x100,0xc0); } for(i = 0xb2 ; i >= 0x30 ; i-- ) { OPNWriteReg(OPN,i ,0); OPNWriteReg(OPN,i|0x100,0); } OPNWriteMode(OPN,0x27,0x30); /* mode 0 , timer reset */ for(i = 0x26 ; i >= 0x20 ; i-- ) OPNWriteReg(OPN,i,0); /**** ADPCM work initial ****/ for( i = 0; i < 6 ; i++ ) { F2610->adpcm[i].step = (UINT32)((float)(1<OPN.ST.freqbase)/3.0); F2610->adpcm[i].now_addr = 0; F2610->adpcm[i].now_step = 0; F2610->adpcm[i].start = 0; F2610->adpcm[i].end = 0; // F2610->adpcm[i].delta = 21866; F2610->adpcm[i].vol_mul = 0; F2610->adpcm[i].pan = &OPN->out_adpcm[OUTD_CENTER]; /* default center */ F2610->adpcm[i].flagMask = 1<adpcm[i].flag = 0; F2610->adpcm[i].adpcm_acc = 0; F2610->adpcm[i].adpcm_step= 0; F2610->adpcm[i].adpcm_out = 0; } F2610->adpcmTL = 0x3f; F2610->adpcm_arrivedEndAddress = 0; /* DELTA-T unit */ DELTAT->freqbase = OPN->ST.freqbase; YM_DELTAT_ADPCM_Reset(DELTAT,OUTD_CENTER); } /* YM2610 write */ /* n = number */ /* a = address */ /* v = value */ void ym2610_write(void *chip, UINT8 a, UINT8 v) { YM2610 *F2610 = (YM2610 *)chip; FM_OPN *OPN = &F2610->OPN; int addr; int ch; //v &= 0xff; /* adjust to 8 bit bus */ switch( a&3 ) { case 0: /* address port 0 */ OPN->ST.address = v; F2610->addr_A1 = 0; /* Write register to SSG emulator */ if( v < 16 ) OPN->ST.SSG_funcs.write(OPN->ST.SSG_param,a,v); break; case 1: /* data port 0 */ if (F2610->addr_A1 != 0) break; /* verified on real YM2608 */ addr = OPN->ST.address; F2610->REGS[addr] = v; switch(addr & 0xf0) { case 0x00: /* SSG section */ /* Write data to SSG emulator */ OPN->ST.SSG_funcs.write(OPN->ST.SSG_param,a,v); break; case 0x10: /* DeltaT ADPCM */ ym2610_update_req(F2610); switch(addr) { case 0x10: /* control 1 */ case 0x11: /* control 2 */ case 0x12: /* start address L */ case 0x13: /* start address H */ case 0x14: /* stop address L */ case 0x15: /* stop address H */ case 0x19: /* delta-n L */ case 0x1a: /* delta-n H */ case 0x1b: /* volume */ { YM_DELTAT_ADPCM_Write(&F2610->deltaT,addr-0x10,v); } break; case 0x1c: /* FLAG CONTROL : Extend Status Clear/Mask */ { UINT8 statusmask = ~v; /* set arrived flag mask */ for(ch=0;ch<6;ch++) F2610->adpcm[ch].flagMask = statusmask&(1<deltaT.status_change_EOS_bit = statusmask & 0x80; /* status flag: set bit7 on End Of Sample */ /* clear arrived flag */ F2610->adpcm_arrivedEndAddress &= statusmask; } break; default: logerror("YM2610: write to unknown deltat register %02x val=%02x\n",addr,v); break; } break; case 0x20: /* Mode Register */ ym2610_update_req(F2610); OPNWriteMode(OPN,addr,v); break; default: /* OPN section */ ym2610_update_req(F2610); /* write register */ OPNWriteReg(OPN,addr,v); } break; case 2: /* address port 1 */ OPN->ST.address = v; F2610->addr_A1 = 1; break; case 3: /* data port 1 */ if (F2610->addr_A1 != 1) break; /* verified on real YM2608 */ ym2610_update_req(F2610); addr = OPN->ST.address; F2610->REGS[addr | 0x100] = v; if( addr < 0x30 ) /* 100-12f : ADPCM A section */ FM_ADPCMAWrite(F2610,addr,v); else OPNWriteReg(OPN,addr | 0x100,v); } //return OPN->ST.irq; return; } UINT8 ym2610_read(void *chip,UINT8 a) { YM2610 *F2610 = (YM2610 *)chip; UINT8 addr = F2610->OPN.ST.address; UINT8 ret = 0; switch( a&3) { case 0: /* status 0 : YM2203 compatible */ ret = FM_STATUS_FLAG(&F2610->OPN.ST) & 0x83; break; case 1: /* data 0 */ if( addr < 16 ) ret = F2610->OPN.ST.SSG_funcs.read(F2610->OPN.ST.SSG_param, 0); else if( addr == 0xff ) ret = 0x01; break; case 2: /* status 1 : ADPCM status */ /* ADPCM STATUS (arrived End Address) */ /* B,--,A5,A4,A3,A2,A1,A0 */ /* B = ADPCM-B(DELTA-T) arrived end address */ /* A0-A5 = ADPCM-A arrived end address */ ret = F2610->adpcm_arrivedEndAddress; break; case 3: ret = 0; break; } return ret; } UINT8 ym2610_timer_over(void *chip,UINT8 c) { YM2610 *F2610 = (YM2610 *)chip; if( c ) { /* Timer B */ TimerBOver( &(F2610->OPN.ST) ); } else { /* Timer A */ ym2610_update_req(F2610); /* timer update */ TimerAOver( &(F2610->OPN.ST) ); /* CSM mode key,TL controll */ if ((F2610->OPN.ST.mode & 0xc0) == 0x80) { /* CSM mode total level latch and auto key on */ CSMKeyControll( &F2610->OPN, &(F2610->CH[2]) ); } } return F2610->OPN.ST.irq; } void ym2610_alloc_pcmroma(void* chip, UINT32 memsize) { YM2610 *F2610 = (YM2610 *)chip; if (F2610->pcm_size == memsize) return; F2610->pcmbuf = (UINT8*)realloc(F2610->pcmbuf, memsize); F2610->pcm_size = memsize; memset(F2610->pcmbuf, 0xFF, memsize); return; } void ym2610_write_pcmroma(void* chip, UINT32 offset, UINT32 length, const UINT8* data) { YM2610 *F2610 = (YM2610 *)chip; if (offset > F2610->pcm_size) return; if (offset + length > F2610->pcm_size) length = F2610->pcm_size - offset; memcpy(F2610->pcmbuf + offset, data, length); return; } void ym2610_alloc_pcmromb(void* chip, UINT32 memsize) { YM2610 *F2610 = (YM2610 *)chip; if (F2610->deltaT.memory_size == memsize) return; F2610->deltaT.memory = (UINT8*)realloc(F2610->deltaT.memory, memsize); F2610->deltaT.memory_size = memsize; memset(F2610->deltaT.memory, 0xFF, memsize); YM_DELTAT_calc_mem_mask(&F2610->deltaT); return; } void ym2610_write_pcmromb(void* chip, UINT32 offset, UINT32 length, const UINT8* data) { YM2610 *F2610 = (YM2610 *)chip; if (offset > F2610->deltaT.memory_size) return; if (offset + length > F2610->deltaT.memory_size) length = F2610->deltaT.memory_size - offset; memcpy(F2610->deltaT.memory + offset, data, length); return; } void ym2610_set_mutemask(void *chip, UINT32 MuteMask) { YM2610 *F2610 = (YM2610 *)chip; UINT8 CurChn; for (CurChn = 0; CurChn < 6; CurChn ++) F2610->CH[CurChn].Muted = (MuteMask >> CurChn) & 0x01; for (CurChn = 0; CurChn < 6; CurChn ++) F2610->adpcm[CurChn].Muted = (MuteMask >> (CurChn + 6)) & 0x01; F2610->MuteDeltaT = (MuteMask >> 12) & 0x01; return; } #endif /* (BUILD_YM2610||BUILD_YM2610B) */ #if (BUILD_YM2612) /*******************************************************************************/ /* YM2612 local section */ /*******************************************************************************/ /* here's the virtual YM2612 */ typedef struct { DEV_DATA _devData; UINT8 REGS[512]; /* registers */ FM_OPN OPN; /* OPN state */ FM_CH CH[6]; /* channel state */ UINT8 addr_A1; /* address line A1 */ /* dac output (YM2612) */ UINT8 dacen; UINT8 dac_test; INT32 dacout; UINT8 MuteDAC; UINT8 WaveOutMode; INT32 WaveL; INT32 WaveR; } YM2612; /* Generate samples for one of the YM2612s */ void ym2612_update_one(void *chip, UINT32 length, DEV_SMPL **buffer) { YM2612 *F2612 = (YM2612 *)chip; FM_OPN *OPN = &F2612->OPN; INT32 *out_fm = OPN->out_fm; UINT32 i; DEV_SMPL *bufL,*bufR; INT32 dacout; FM_CH *cch[6]; INT32 lt,rt; /* set buffer */ if (buffer != NULL) { bufL = buffer[0]; bufR = buffer[1]; } else { // for internal 0-sample update bufL = bufR = NULL; } cch[0] = &F2612->CH[0]; cch[1] = &F2612->CH[1]; cch[2] = &F2612->CH[2]; cch[3] = &F2612->CH[3]; cch[4] = &F2612->CH[4]; cch[5] = &F2612->CH[5]; if (! F2612->MuteDAC) dacout = F2612->dacout << 5; /* level unknown */ else dacout = 0; /* refresh PG and EG */ refresh_fc_eg_chan( OPN, cch[0] ); refresh_fc_eg_chan( OPN, cch[1] ); if( (OPN->ST.mode & 0xc0) ) { /* 3SLOT MODE */ if( cch[2]->SLOT[SLOT1].Incr==-1) { refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT1] , OPN->SL3.fc[1] , OPN->SL3.kcode[1] ); refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT2] , OPN->SL3.fc[2] , OPN->SL3.kcode[2] ); refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT3] , OPN->SL3.fc[0] , OPN->SL3.kcode[0] ); refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT4] , cch[2]->fc , cch[2]->kcode ); } } else refresh_fc_eg_chan( OPN, cch[2] ); refresh_fc_eg_chan( OPN, cch[3] ); refresh_fc_eg_chan( OPN, cch[4] ); refresh_fc_eg_chan( OPN, cch[5] ); if (! length) { update_ssg_eg_channel(&cch[0]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[1]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[2]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[3]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[4]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[5]->SLOT[SLOT1]); } /* buffering */ for(i=0; i < length ; i++) { /* clear outputs */ out_fm[0] = 0; out_fm[1] = 0; out_fm[2] = 0; out_fm[3] = 0; out_fm[4] = 0; out_fm[5] = 0; /* update SSG-EG output */ update_ssg_eg_channel(&cch[0]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[1]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[2]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[3]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[4]->SLOT[SLOT1]); update_ssg_eg_channel(&cch[5]->SLOT[SLOT1]); /* calculate FM */ if (! F2612->dac_test) { chan_calc(OPN, cch[0], 0 ); chan_calc(OPN, cch[1], 1 ); chan_calc(OPN, cch[2], 2 ); chan_calc(OPN, cch[3], 3 ); chan_calc(OPN, cch[4], 4 ); if( F2612->dacen ) *cch[5]->connect4 += dacout; else chan_calc(OPN, cch[5], 5 ); } else { out_fm[0] = out_fm[1] = dacout; out_fm[2] = out_fm[3] = dacout; out_fm[5] = dacout; } /* advance LFO */ advance_lfo(OPN); /* advance envelope generator */ OPN->eg_timer += OPN->eg_timer_add; while (OPN->eg_timer >= OPN->eg_timer_overflow) { OPN->eg_timer -= OPN->eg_timer_overflow; OPN->eg_cnt++; advance_eg_channel(OPN, &cch[0]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[1]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[2]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[3]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[4]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[5]->SLOT[SLOT1]); } if (out_fm[0] > 8192) out_fm[0] = 8192; else if (out_fm[0] < -8192) out_fm[0] = -8192; if (out_fm[1] > 8192) out_fm[1] = 8192; else if (out_fm[1] < -8192) out_fm[1] = -8192; if (out_fm[2] > 8192) out_fm[2] = 8192; else if (out_fm[2] < -8192) out_fm[2] = -8192; if (out_fm[3] > 8192) out_fm[3] = 8192; else if (out_fm[3] < -8192) out_fm[3] = -8192; if (out_fm[4] > 8192) out_fm[4] = 8192; else if (out_fm[4] < -8192) out_fm[4] = -8192; if (out_fm[5] > 8192) out_fm[5] = 8192; else if (out_fm[5] < -8192) out_fm[5] = -8192; /* 6-channels mixing */ lt = ((out_fm[0]>>0) & OPN->pan[0]); rt = ((out_fm[0]>>0) & OPN->pan[1]); lt += ((out_fm[1]>>0) & OPN->pan[2]); rt += ((out_fm[1]>>0) & OPN->pan[3]); lt += ((out_fm[2]>>0) & OPN->pan[4]); rt += ((out_fm[2]>>0) & OPN->pan[5]); lt += ((out_fm[3]>>0) & OPN->pan[6]); rt += ((out_fm[3]>>0) & OPN->pan[7]); if (! F2612->dac_test) { lt += ((out_fm[4]>>0) & OPN->pan[8]); rt += ((out_fm[4]>>0) & OPN->pan[9]); } else { // DAC test mode ignores panning for channel 4 lt += dacout; lt += dacout; } lt += ((out_fm[5]>>0) & OPN->pan[10]); rt += ((out_fm[5]>>0) & OPN->pan[11]); /* buffering */ if (F2612->WaveOutMode) { if (F2612->WaveOutMode & 0x01) F2612->WaveL = lt; if (F2612->WaveOutMode & 0x02) F2612->WaveR = rt; F2612->WaveOutMode ^= 0x03; } else { F2612->WaveL = lt; F2612->WaveR = rt; } bufL[i] = F2612->WaveL; bufR[i] = F2612->WaveR; /* CSM mode: if CSM Key ON has occured, CSM Key OFF need to be sent */ /* only if Timer A does not overflow again (i.e CSM Key ON not set again) */ OPN->SL3.key_csm <<= 1; /* timer A control */ INTERNAL_TIMER_A( &OPN->ST , cch[2] ) /* CSM Mode Key ON still disabled */ if (OPN->SL3.key_csm & 2) { /* CSM Mode Key OFF (verified by Nemesis on real hardware) */ FM_KEYOFF_CSM(cch[2],SLOT1); FM_KEYOFF_CSM(cch[2],SLOT2); FM_KEYOFF_CSM(cch[2],SLOT3); FM_KEYOFF_CSM(cch[2],SLOT4); OPN->SL3.key_csm = 0; } } /* timer B control */ INTERNAL_TIMER_B(&OPN->ST,length) } static void ym2612_update_req(void *param) { ym2612_update_one(param, 0, NULL); } /* initialize YM2612 emulator(s) */ void * ym2612_init(void *param, UINT32 clock, UINT32 rate, FM_TIMERHANDLER timer_handler,FM_IRQHANDLER IRQHandler) { YM2612 *F2612; /* allocate extend state space */ F2612 = (YM2612 *)calloc(1, sizeof(YM2612)); if (F2612 == NULL) return NULL; /* allocate total level table (128kb space) */ init_tables(); /* FM */ F2612->OPN.ST.param = param; F2612->OPN.type = TYPE_YM2612; F2612->OPN.P_CH = F2612->CH; F2612->OPN.ST.clock = clock; F2612->OPN.ST.rate = rate; OPNCheckNativeSampleRate(&F2612->OPN); /* Extend handler */ F2612->OPN.ST.timer_handler = timer_handler; F2612->OPN.ST.IRQ_Handler = IRQHandler; F2612->OPN.LegacyMode = 0x00; F2612->WaveOutMode = 0x00; OPNLinkSSG(&F2612->OPN, NULL, NULL); OPNSetSmplRateChgCallback(&F2612->OPN, NULL, NULL); ym2612_set_mutemask(F2612, 0x00); return F2612; } /* shut down emulator */ void ym2612_shutdown(void *chip) { YM2612 *F2612 = (YM2612 *)chip; free(F2612); } /* reset one of chip */ void ym2612_reset_chip(void *chip) { int i; YM2612 *F2612 = (YM2612 *)chip; FM_OPN *OPN = &F2612->OPN; /* Reset Prescaler */ OPNSetPres( OPN, 6*24, 6*24, 0); /* status clear */ FM_IRQMASK_SET(&OPN->ST,0x03); FM_BUSY_CLEAR(&OPN->ST); OPN->eg_timer = 0; OPN->eg_cnt = 0; OPN->ST.TAC = 0; OPN->ST.TBC = 0; OPN->SL3.key_csm = 0; OPN->ST.status = 0; OPN->ST.mode = 0; OPN->ST.irq = 0; memset(F2612->REGS, 0x00, sizeof(UINT8) * 512); reset_channels( &OPN->ST , F2612->CH , 6 ); /* reset Operator paramater */ for(i = 0xb6 ; i >= 0xb4 ; i-- ) { OPNWriteReg(OPN,i ,0xc0); OPNWriteReg(OPN,i|0x100,0xc0); } for(i = 0xb2 ; i >= 0x30 ; i-- ) { OPNWriteReg(OPN,i ,0); OPNWriteReg(OPN,i|0x100,0); } OPNWriteMode(OPN,0x27,0x30); /* mode 0 , timer reset */ for(i = 0x26 ; i >= 0x20 ; i-- ) OPNWriteReg(OPN,i,0); /* DAC mode clear */ F2612->dacen = 0; F2612->dac_test = 0; F2612->dacout = 0; F2612->WaveOutMode >>= 1; F2612->WaveL = F2612->WaveR = 0; } /* YM2612 write */ /* n = number */ /* a = address */ /* v = value */ void ym2612_write(void *chip, UINT8 a, UINT8 v) { YM2612 *F2612 = (YM2612 *)chip; FM_OPN *OPN = &F2612->OPN; int addr; //v &= 0xff; /* adjust to 8 bit bus */ switch( a&3 ) { case 0: /* address port 0 */ OPN->ST.address = v; F2612->addr_A1 = 0; /* Write register to SSG emulator */ if( v < 16 ) OPN->ST.SSG_funcs.write(OPN->ST.SSG_param,a,v); break; case 1: /* data port 0 */ if (F2612->addr_A1 != 0) break; /* verified on real YM2608 */ addr = OPN->ST.address; F2612->REGS[addr] = v; switch(addr & 0xf0) { case 0x20: /* 0x20-0x2f Mode */ switch( addr ) { case 0x2a: /* DAC data (YM2612) */ //ym2612_update_req(F2612); F2612->dacout &= 0x01; // set high 8 bit of the 9-bit DAC F2612->dacout |= ((int)v - 0x80) << 1; /* level unknown */ break; case 0x2b: /* DAC Sel (YM2612) */ /* b7 = dac enable */ F2612->dacen = v & 0x80; break; case 0x2C: // undocumented: DAC Test Register // http://gendev.spritesmind.net/forum/viewtopic.php?p=26996#p26996 // b7/b6/b4 = various test bits // b5 = makes DAC go to channels 0..3 and 5. // b3 = 9th DAC bit F2612->dac_test = v & 0x20; F2612->dacout &= ~0x01; // set DAC's 9th bit F2612->dacout |= (v & 0x08) >> 3; break; default: /* OPN section */ ym2612_update_req(F2612); /* write register */ OPNWriteMode(OPN,addr,v); } break; default: /* 0x30-0xff OPN section */ ym2612_update_req(F2612); /* write register */ OPNWriteReg(OPN,addr,v); } break; case 2: /* address port 1 */ OPN->ST.address = v; F2612->addr_A1 = 1; break; case 3: /* data port 1 */ if (F2612->addr_A1 != 1) break; /* verified on real YM2608 */ ym2612_update_req(F2612); addr = OPN->ST.address; F2612->REGS[addr | 0x100] = v; OPNWriteReg(OPN,addr | 0x100,v); break; } //return OPN->ST.irq; return; } UINT8 ym2612_read(void *chip, UINT8 a) { YM2612 *F2612 = (YM2612 *)chip; switch( a&3) { case 0: /* status 0 : YM2203 compatible */ return FM_STATUS_FLAG(&F2612->OPN.ST); case 1: case 2: case 3: //LOG(LOG_WAR,("YM2612 #%p:A=%d read unmapped area\n",F2612->OPN.ST.param,a)); return FM_STATUS_FLAG(&F2612->OPN.ST); } return 0; } UINT8 ym2612_timer_over(void *chip,UINT8 c) { YM2612 *F2612 = (YM2612 *)chip; if( c ) { /* Timer B */ TimerBOver( &(F2612->OPN.ST) ); } else { /* Timer A */ ym2612_update_req(F2612); /* timer update */ TimerAOver( &(F2612->OPN.ST) ); /* CSM mode key,TL controll */ if ((F2612->OPN.ST.mode & 0xc0) == 0x80) { /* CSM mode total level latch and auto key on */ CSMKeyControll( &F2612->OPN, &(F2612->CH[2]) ); } } return F2612->OPN.ST.irq; } void ym2612_set_mutemask(void *chip, UINT32 MuteMask) { YM2612 *F2612 = (YM2612 *)chip; UINT8 CurChn; for (CurChn = 0; CurChn < 6; CurChn ++) F2612->CH[CurChn].Muted = (MuteMask >> CurChn) & 0x01; F2612->MuteDAC = (MuteMask >> 6) & 0x01; return; } void ym2612_setoptions(void *chip, UINT32 Flags) { YM2612 *F2612 = (YM2612 *)chip; UINT8 PseudoStereo; PseudoStereo = (Flags >> 2) & 0x01; F2612->WaveOutMode = (PseudoStereo) ? 0x01 : 0x00; F2612->OPN.LegacyMode = (Flags >> 7) & 0x01; return; } #endif /* (BUILD_YM2612) */ BambooTracker-0.6.5/BambooTracker/chip/mame/fmopn.h000066400000000000000000000135441476276175200221320ustar00rootroot00000000000000#ifndef __FMOPN_H__ #define __FMOPN_H__ #include "mamedefs.h" /* --- select emulation chips --- */ //#define BUILD_YM2203 1 // build YM2203(OPN) emulator #define BUILD_YM2608 1 // build YM2608(OPNA) emulator //#define BUILD_YM2610 1 // build YM2610(OPNB) emulator //#define BUILD_YM2610B 1 // build YM2610B(OPNB?)emulator //#define BUILD_YM2612 1 // build YM2612(OPN2) emulator //#define BUILD_YM3438 1 // build YM3438(OPN2C) emulator /* select timer system internal or external */ #define FM_INTERNAL_TIMER 1 /* --- speedup optimize --- */ /* busy flag emulation , The definition of FM_GET_TIME_NOW() is necessary. */ //#define FM_BUSY_FLAG_SUPPORT 1 /* --- external SSG(YM2149/AY-3-8910)emulator interface port */ /* used by YM2203,YM2608,and YM2610 */ typedef struct _ssg_callbacks ssg_callbacks; struct _ssg_callbacks { void (*set_clock)(void *param, UINT32 clock); void (*write)(void *param, UINT8 address, UINT8 data); UINT8 (*read)(void *param); void (*reset)(void *param); }; /* --- external callback functions for realtime update --- */ #if FM_BUSY_FLAG_SUPPORT #define TIME_TYPE attotime #define UNDEFINED_TIME attotime::zero #define FM_GET_TIME_NOW(machine) (machine)->time() #define ADD_TIMES(t1, t2) ((t1) + (t2)) #define COMPARE_TIMES(t1, t2) (((t1) == (t2)) ? 0 : ((t1) < (t2)) ? -1 : 1) #define MULTIPLY_TIME_BY_INT(t,i) ((t) * (i)) #endif typedef void (*FM_TIMERHANDLER)(void *param,UINT8 c,INT32 cnt,UINT32 clock); typedef void (*FM_IRQHANDLER)(void *param,UINT8 irq); /* FM_TIMERHANDLER : Stop or Start timer */ /* int n = chip number */ /* int c = Channel 0=TimerA,1=TimerB */ /* int count = timer count (0=stop) */ /* doube stepTime = step time of one count (sec.)*/ /* FM_IRQHHANDLER : IRQ level changing sense */ /* int n = chip number */ /* int irq = IRQ level 0=OFF,1=ON */ #if BUILD_YM2203 /* -------------------- YM2203(OPN) Interface -------------------- */ /* ** Initialize YM2203 emulator(s). ** ** 'num' is the number of virtual YM2203's to allocate ** 'baseclock' ** 'rate' is sampling rate ** 'TimerHandler' timer callback handler when timer start and clear ** 'IRQHandler' IRQ callback handler when changed IRQ level ** return 0 = success */ void * ym2203_init(void *param, UINT32 baseclock, UINT32 rate, FM_TIMERHANDLER TimerHandler,FM_IRQHANDLER IRQHandler); /* ** link SSG callbacks into the YM2203 */ void ym2203_link_ssg(void *chip, const ssg_callbacks *ssg, void *ssg_param); /* ** set the callback function for YM2203 sample rate changes */ void ym2203_set_srchg_cb(void *chip, DEVCB_SRATE_CHG cbFunc, void* dataPtr); /* ** shutdown the YM2203 emulators */ void ym2203_shutdown(void *chip); /* ** reset all chip registers for YM2203 number 'num' */ void ym2203_reset_chip(void *chip); /* ** update one of chip */ void ym2203_update_one(void *chip, UINT32 length, DEV_SMPL **buffer); /* ** Write ** return : InterruptLevel */ void ym2203_write(void *chip, UINT8 a, UINT8 v); /* ** Read ** return : InterruptLevel */ UINT8 ym2203_read(void *chip, UINT8 a); /* ** Timer OverFlow */ UINT8 ym2203_timer_over(void *chip, UINT8 c); /* ** Channel Muting */ void ym2203_set_mutemask(void *chip, UINT32 MuteMask); #endif /* BUILD_YM2203 */ #if BUILD_YM2608 /* -------------------- YM2608(OPNA) Interface -------------------- */ void * ym2608_init(void *param, UINT32 baseclock, UINT32 rate, FM_TIMERHANDLER TimerHandler,FM_IRQHANDLER IRQHandler); void ym2608_link_ssg(void *chip, const ssg_callbacks *ssg, void *ssg_param); //void ym2608_set_srchg_cb(void *chip, DEVCB_SRATE_CHG cbFunc, void* dataPtr); void ym2608_shutdown(void *chip); void ym2608_reset_chip(void *chip); void ym2608_update_one(void *chip, UINT32 length, stream_sample_t **buffer); void ym2608_write(void *chip, UINT8 a, UINT8 v); UINT8 ym2608_read(void *chip, UINT8 a); UINT8 ym2608_timer_over(void *chip, UINT8 c ); void ym2608_alloc_pcmromb(void* chip, UINT32 memsize); void ym2608_write_pcmromb(void* chip, UINT32 offset, UINT32 length, const UINT8* data); void ym2608_set_mutemask(void *chip, UINT32 MuteMask); #endif /* BUILD_YM2608 */ #if (BUILD_YM2610||BUILD_YM2610B) /* -------------------- YM2610(OPNB) Interface -------------------- */ void * ym2610_init(void *param, UINT32 baseclock, UINT32 rate, FM_TIMERHANDLER TimerHandler,FM_IRQHANDLER IRQHandler); void ym2610_link_ssg(void *chip, const ssg_callbacks *ssg, void *ssg_param); void ym2610_shutdown(void *chip); void ym2610_reset_chip(void *chip); void ym2610_update_one(void *chip, UINT32 length, DEV_SMPL **buffer); #if BUILD_YM2610B void ym2610b_update_one(void *chip, UINT32 length, DEV_SMPL **buffer); #endif /* BUILD_YM2610B */ void ym2610_write(void *chip, UINT8 a, UINT8 v); UINT8 ym2610_read(void *chip, UINT8 a); UINT8 ym2610_timer_over(void *chip, UINT8 c ); void ym2610_alloc_pcmroma(void* chip, UINT32 memsize); void ym2610_write_pcmroma(void* chip, UINT32 offset, UINT32 length, const UINT8* data); void ym2610_alloc_pcmromb(void* chip, UINT32 memsize); void ym2610_write_pcmromb(void* chip, UINT32 offset, UINT32 length, const UINT8* data); void ym2610_set_mutemask(void *chip, UINT32 MuteMask); #endif /* (BUILD_YM2610||BUILD_YM2610B) */ #if (BUILD_YM2612||BUILD_YM3438) void * ym2612_init(void *param, UINT32 baseclock, UINT32 rate, FM_TIMERHANDLER TimerHandler,FM_IRQHANDLER IRQHandler); void ym2612_shutdown(void *chip); void ym2612_reset_chip(void *chip); void ym2612_update_one(void *chip, UINT32 length, DEV_SMPL **buffer); void ym2612_write(void *chip, UINT8 a, UINT8 v); UINT8 ym2612_read(void *chip, UINT8 a); UINT8 ym2612_timer_over(void *chip, UINT8 c ); void ym2612_set_mutemask(void *chip, UINT32 MuteMask); void ym2612_setoptions(void *chip, UINT32 Flags); #endif /* (BUILD_YM2612||BUILD_YM3438) */ #endif // __FMOPN_H__ BambooTracker-0.6.5/BambooTracker/chip/mame/fmopn_2608rom.h000066400000000000000000001221011476276175200233150ustar00rootroot00000000000000/* This data is derived from the chip's output - internal ROM can't be read. It was verified, using real YM2608, that this ADPCM stream produces 100% correct output signal. */ const unsigned char YM2608_ADPCM_ROM[0x2000] = { /* Source: 01BD.ROM */ /* Length: 448 / 0x000001C0 */ 0x88,0x08,0x08,0x08,0x00,0x88,0x16,0x76,0x99,0xB8,0x22,0x3A,0x84,0x3C,0xB1,0x54, 0x10,0xA9,0x98,0x32,0x80,0x33,0x9A,0xA7,0x4A,0xB4,0x58,0xBC,0x15,0x29,0x8A,0x97, 0x9B,0x44,0xAC,0x80,0x12,0xDE,0x13,0x1B,0xC0,0x58,0xC8,0x11,0x0A,0xA2,0x1A,0xA0, 0x00,0x98,0x0B,0x93,0x9E,0x92,0x0A,0x88,0xBE,0x14,0x1B,0x98,0x08,0xA1,0x4A,0xC1, 0x30,0xD9,0x33,0x98,0x10,0x89,0x17,0x1A,0x82,0x29,0x37,0x0C,0x83,0x50,0x9A,0x24, 0x1A,0x83,0x10,0x23,0x19,0xB3,0x72,0x8A,0x16,0x10,0x0A,0x93,0x70,0x99,0x23,0x99, 0x02,0x20,0x91,0x18,0x02,0x41,0xAB,0x24,0x18,0x81,0x99,0x4A,0xE8,0x28,0x9A,0x99, 0xA1,0x2F,0xA8,0x9D,0x90,0x08,0xCC,0xA3,0x1D,0xCA,0x82,0x0B,0xD8,0x08,0xB9,0x09, 0xBC,0xB8,0x00,0xBE,0x90,0x1B,0xCA,0x00,0x9B,0x8A,0xA8,0x91,0x0F,0xB3,0x3D,0xB8, 0x31,0x0B,0xA5,0x0A,0x11,0xA1,0x48,0x92,0x10,0x50,0x91,0x30,0x23,0x09,0x37,0x39, 0xA2,0x72,0x89,0x92,0x30,0x83,0x1C,0x96,0x28,0xB9,0x24,0x8C,0xA1,0x31,0xAD,0xA9, 0x13,0x9C,0xBA,0xA8,0x0B,0xBF,0xB8,0x9B,0xCA,0x88,0xDB,0xB8,0x19,0xFC,0x92,0x0A, 0xBA,0x89,0xAB,0xB8,0xAB,0xD8,0x08,0xAD,0xBA,0x33,0x9D,0xAA,0x83,0x3A,0xC0,0x40, 0xB9,0x15,0x39,0xA2,0x52,0x89,0x02,0x63,0x88,0x13,0x23,0x03,0x52,0x02,0x54,0x00, 0x11,0x23,0x23,0x35,0x20,0x01,0x44,0x41,0x80,0x24,0x40,0xA9,0x45,0x19,0x81,0x12, 0x81,0x02,0x11,0x21,0x19,0x02,0x61,0x8A,0x13,0x3A,0x10,0x12,0x23,0x8B,0x37,0x18, 0x91,0x24,0x10,0x81,0x34,0x20,0x05,0x32,0x82,0x53,0x20,0x14,0x33,0x31,0x34,0x52, 0x00,0x43,0x32,0x13,0x52,0x22,0x13,0x52,0x11,0x43,0x11,0x32,0x32,0x32,0x22,0x02, 0x13,0x12,0x89,0x22,0x19,0x81,0x81,0x08,0xA8,0x08,0x8B,0x90,0x1B,0xBA,0x8A,0x9B, 0xB9,0x89,0xCA,0xB9,0xAB,0xCA,0x9B,0xCA,0xB9,0xAB,0xDA,0x99,0xAC,0xBB,0x9B,0xAC, 0xAA,0xBA,0xAC,0xAB,0x9A,0xAA,0xAA,0xBA,0xB8,0xA9,0xBA,0x99,0xA9,0x9A,0xA0,0x8A, 0xA9,0x08,0x8A,0xA9,0x00,0x99,0x89,0x88,0x98,0x08,0x99,0x00,0x89,0x80,0x08,0x98, 0x00,0x88,0x88,0x80,0x90,0x80,0x90,0x80,0x81,0x99,0x08,0x88,0x99,0x09,0x00,0x1A, 0xA8,0x10,0x9A,0x88,0x08,0x0A,0x8A,0x89,0x99,0xA8,0x98,0xA9,0x99,0x99,0xA9,0x99, 0xAA,0x8A,0xAA,0x9B,0x8A,0x9A,0xA9,0x9A,0xBA,0x99,0x9A,0xAA,0x99,0x89,0xA9,0x99, 0x98,0x9A,0x98,0x88,0x09,0x89,0x09,0x08,0x08,0x09,0x18,0x18,0x00,0x12,0x00,0x11, 0x11,0x11,0x12,0x12,0x21,0x21,0x22,0x22,0x22,0x22,0x22,0x22,0x32,0x31,0x32,0x31, 0x32,0x32,0x21,0x31,0x21,0x32,0x21,0x12,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80, /* Source: 02SD.ROM */ /* Length: 640 / 0x00000280 */ 0x0A,0xDC,0x14,0x0B,0xBA,0xBC,0x01,0x0F,0xF5,0x2F,0x87,0x19,0xC9,0x24,0x1B,0xA1, 0x31,0x99,0x90,0x32,0x32,0xFE,0x83,0x48,0xA8,0xA9,0x23,0x19,0xBC,0x91,0x02,0x41, 0xDE,0x81,0x28,0xA8,0x0A,0xB1,0x72,0xDA,0x23,0xBC,0x04,0x19,0xB8,0x21,0x8A,0x03, 0x29,0xBA,0x14,0x21,0x0B,0xC0,0x43,0x08,0x91,0x50,0x93,0x0F,0x86,0x1A,0x91,0x18, 0x21,0xCB,0x27,0x0A,0xA1,0x42,0x8C,0xA9,0x21,0x10,0x08,0xAB,0x94,0x2A,0xDA,0x02, 0x8B,0x91,0x09,0x98,0xAE,0x80,0xA9,0x02,0x0A,0xE9,0x21,0xBB,0x15,0x20,0xBE,0x92, 0x42,0x09,0xA9,0x11,0x34,0x08,0x12,0x0A,0x27,0x29,0xA1,0x52,0x12,0x8E,0x92,0x28, 0x92,0x2B,0xD1,0x23,0xBF,0x81,0x10,0x99,0xA8,0x0A,0xC4,0x3B,0xB9,0xB0,0x00,0x62, 0xCF,0x92,0x29,0x92,0x2B,0xB1,0x1C,0xB2,0x72,0xAA,0x88,0x11,0x18,0x80,0x13,0x9E, 0x03,0x18,0xB0,0x60,0xA1,0x28,0x88,0x08,0x04,0x10,0x8F,0x96,0x19,0x90,0x01,0x09, 0xC8,0x50,0x91,0x8A,0x01,0xAB,0x03,0x50,0xBA,0x9D,0x93,0x68,0xBA,0x80,0x22,0xCB, 0x41,0xBC,0x92,0x60,0xB9,0x1A,0x95,0x4A,0xC8,0x20,0x88,0x33,0xAC,0x92,0x38,0x83, 0x09,0x80,0x16,0x09,0x29,0xD0,0x54,0x8C,0xA2,0x28,0x91,0x89,0x93,0x60,0xCD,0x85, 0x1B,0xA1,0x49,0x90,0x8A,0x80,0x34,0x0C,0xC9,0x14,0x19,0x98,0xA0,0x40,0xA9,0x21, 0xD9,0x34,0x0A,0xA9,0x10,0x23,0xCB,0x25,0xAA,0x25,0x9B,0x13,0xCD,0x16,0x09,0xA0, 0x80,0x01,0x19,0x90,0x88,0x21,0xAC,0x33,0x8B,0xD8,0x27,0x3B,0xB8,0x81,0x31,0x80, 0xAF,0x97,0x0A,0x82,0x0A,0xA0,0x21,0x89,0x8A,0xA2,0x32,0x8D,0xBB,0x87,0x19,0x21, 0xC9,0xBC,0x45,0x09,0x90,0x09,0xA1,0x24,0x1A,0xD0,0x10,0x08,0x11,0xA9,0x21,0xE8, 0x60,0xA9,0x14,0x0C,0xD1,0x32,0xAB,0x04,0x0C,0x81,0x90,0x29,0x83,0x9B,0x01,0x8F, 0x97,0x0B,0x82,0x18,0x88,0xBA,0x06,0x39,0xC8,0x23,0xBC,0x04,0x09,0x92,0x08,0x1A, 0xBB,0x74,0x8C,0x81,0x18,0x81,0x9D,0x83,0x41,0xCD,0x81,0x40,0x9A,0x90,0x10,0x12, 0x9C,0xA1,0x68,0xD8,0x33,0x9C,0x91,0x01,0x12,0xBE,0x02,0x09,0x12,0x99,0x9A,0x36, 0x0A,0xB0,0x30,0x88,0xA3,0x2D,0x12,0xBC,0x03,0x3A,0x11,0xBD,0x08,0xC8,0x62,0x80, 0x8B,0xD8,0x23,0x38,0xF9,0x12,0x08,0x99,0x91,0x21,0x99,0x85,0x2F,0xB2,0x30,0x90, 0x88,0xD9,0x53,0xAC,0x82,0x19,0x91,0x20,0xCC,0x96,0x29,0xC9,0x24,0x89,0x80,0x99, 0x12,0x08,0x18,0x88,0x99,0x23,0xAB,0x73,0xCB,0x33,0x9F,0x04,0x2B,0xB1,0x08,0x03, 0x1B,0xC9,0x21,0x32,0xFA,0x33,0xDB,0x02,0x33,0xAE,0xB9,0x54,0x8B,0xA1,0x20,0x89, 0x90,0x11,0x88,0x09,0x98,0x23,0xBE,0x37,0x8D,0x81,0x20,0xAA,0x34,0xBB,0x13,0x18, 0xB9,0x40,0xB1,0x18,0x83,0x8E,0xB2,0x72,0xBC,0x82,0x30,0xA9,0x9A,0x24,0x8B,0x27, 0x0E,0x91,0x20,0x90,0x08,0xB0,0x32,0xB9,0x21,0xB0,0xAC,0x45,0x9A,0xA1,0x50,0xA9, 0x80,0x0A,0x26,0x9B,0x11,0xBB,0x23,0x71,0xCB,0x12,0x10,0xB8,0x40,0xA9,0xA5,0x39, 0xC0,0x30,0xB2,0x20,0xAA,0xBA,0x76,0x1C,0xC1,0x48,0x98,0x80,0x18,0x81,0xAA,0x23, 0x9C,0xA2,0x32,0xAC,0x9A,0x43,0x9C,0x12,0xAD,0x82,0x72,0xBC,0x00,0x82,0x39,0xD1, 0x3A,0xB8,0x35,0x9B,0x10,0x40,0xF9,0x22,0x0A,0xC0,0x51,0xB9,0x82,0x18,0x98,0xA3, 0x79,0xD0,0x20,0x88,0x09,0x01,0x99,0x82,0x11,0x38,0xFC,0x33,0x09,0xC8,0x40,0xA9, 0x11,0x29,0xAA,0x94,0x3A,0xC2,0x4A,0xC0,0x89,0x52,0xBC,0x11,0x08,0x09,0xB8,0x71, 0xA9,0x08,0xA8,0x62,0x8D,0x92,0x10,0x00,0x9E,0x94,0x38,0xBA,0x13,0x88,0x90,0x4A, 0xE2,0x30,0xBA,0x02,0x00,0x19,0xD9,0x62,0xBB,0x04,0x0B,0xA3,0x68,0xB9,0x21,0x88, 0x9D,0x04,0x10,0x8C,0xC8,0x62,0x99,0xAA,0x24,0x1A,0x80,0x9A,0x14,0x9B,0x26,0x8C, 0x92,0x30,0xB9,0x09,0xA3,0x71,0xBB,0x10,0x19,0x82,0x39,0xDB,0x02,0x44,0x9F,0x10, /* Source: 04TOP.ROM */ /* Length: 5952 / 0x00001740 */ 0x07,0xFF,0x7C,0x3C,0x31,0xC6,0xC4,0xBB,0x7F,0x7F,0x7B,0x82,0x8A,0x4D,0x5F,0x7C, 0x3E,0x44,0xD2,0xB3,0xA0,0x19,0x1B,0x6C,0x81,0x28,0xC4,0xA1,0x1C,0x4B,0x18,0x00, 0x2A,0xA2,0x0A,0x7C,0x2A,0x00,0x01,0x89,0x98,0x48,0x8A,0x3C,0x28,0x2A,0x5B,0x3E, 0x3A,0x1A,0x3B,0x3D,0x4B,0x3B,0x4A,0x08,0x2A,0x1A,0x2C,0x4A,0x3B,0x82,0x99,0x3C, 0x5D,0x29,0x2B,0x39,0x0B,0x23,0xAB,0x1A,0x4C,0x79,0xA3,0x01,0xC1,0x2A,0x0A,0x38, 0xA7,0xB9,0x12,0x1F,0x29,0x08,0x82,0xA1,0x08,0xA9,0x42,0xAA,0x95,0xB3,0x90,0x81, 0x09,0xD4,0x1A,0x80,0x1B,0x07,0xB8,0x12,0x8E,0x49,0x81,0x92,0xD3,0x90,0xA1,0x2A, 0x02,0xE1,0xA3,0x99,0x02,0xB3,0x94,0xB3,0xB0,0xF4,0x98,0x93,0x90,0x13,0xE1,0x81, 0x99,0x38,0x91,0xA6,0xD3,0x99,0x94,0xC1,0x83,0xB1,0x92,0x98,0x49,0xC4,0xB2,0xA4, 0xA3,0xD0,0x1A,0x30,0xBA,0x59,0x02,0xD4,0xA0,0xA4,0xA2,0x8A,0x01,0x00,0xB7,0xA8, 0x18,0x2A,0x2B,0x1E,0x23,0xC8,0x1A,0x00,0x39,0xA0,0x18,0x92,0x4F,0x2D,0x5A,0x10, 0x89,0x81,0x2A,0x8B,0x6A,0x02,0x09,0xB3,0x8D,0x48,0x1B,0x80,0x19,0x34,0xF8,0x29, 0x0A,0x7B,0x2A,0x28,0x81,0x0C,0x02,0x1E,0x29,0x09,0x12,0xC2,0x94,0xE1,0x18,0x98, 0x02,0xC4,0x89,0x91,0x1A,0x20,0xA9,0x02,0x1B,0x48,0x8E,0x20,0x88,0x2D,0x08,0x59, 0x1B,0x02,0xA3,0xB1,0x8A,0x1E,0x58,0x80,0xC2,0xB6,0x88,0x91,0x88,0x11,0xA1,0xA3, 0xE2,0x01,0xB0,0x19,0x11,0x09,0xF4,0x88,0x09,0x88,0x19,0x89,0x12,0xF1,0x2A,0x28, 0x8C,0x25,0x99,0xA4,0x98,0x39,0xA1,0x00,0xD0,0x58,0xAA,0x59,0x01,0x0C,0x00,0x2B, 0x00,0x08,0x89,0x6B,0x69,0x90,0x01,0x90,0x98,0x12,0xB3,0xF3,0xA0,0x89,0x02,0x3B, 0x0C,0x50,0xA9,0x4E,0x6B,0x19,0x28,0x09,0xA2,0x08,0x2F,0x20,0x88,0x92,0x8A,0x11, 0xC4,0x93,0xF1,0x18,0x88,0x11,0xF2,0x80,0x92,0xA8,0x02,0xA8,0xB7,0xB3,0xA3,0xA0, 0x88,0x1A,0x40,0xE2,0x91,0x19,0x88,0x18,0x91,0x83,0xC1,0xB5,0x92,0xA9,0xC6,0x90, 0x01,0xC2,0x81,0x98,0x03,0xF0,0x00,0x2C,0x2A,0x92,0x2C,0x83,0x1F,0x3A,0x29,0x00, 0xB8,0x70,0xAB,0x69,0x18,0x89,0x10,0x0D,0x12,0x0B,0x88,0x4A,0x3A,0x9B,0x70,0xA8, 0x28,0x2F,0x2A,0x3A,0x1B,0x85,0x88,0x8B,0x6A,0x29,0x00,0x91,0x91,0x1B,0x7C,0x29, 0x01,0x88,0x90,0x19,0x2B,0x2B,0x00,0x39,0xA8,0x5E,0x21,0x89,0x91,0x09,0x3A,0x6F, 0x2A,0x18,0x18,0x8B,0x50,0x89,0x2B,0x19,0x49,0x88,0x29,0xF5,0x89,0x08,0x09,0x12, 0xAA,0x15,0xB0,0x82,0xAC,0x38,0x00,0x3F,0x81,0x10,0xB0,0x49,0xA2,0x81,0x3A,0xC8, 0x87,0x90,0xC4,0xA3,0x99,0x19,0x83,0xE1,0x84,0xE2,0xA2,0x90,0x80,0x93,0xB5,0xC4, 0xB3,0xA1,0x0A,0x18,0x92,0xC4,0xA0,0x93,0x0C,0x3A,0x18,0x01,0x1E,0x20,0xB1,0x82, 0x8C,0x03,0xB5,0x2E,0x82,0x19,0xB2,0x1B,0x1B,0x6B,0x4C,0x19,0x12,0x8B,0x5A,0x11, 0x0C,0x3A,0x2C,0x18,0x3D,0x08,0x2A,0x5C,0x18,0x00,0x88,0x3D,0x29,0x80,0x2A,0x09, 0x00,0x7A,0x0A,0x10,0x0B,0x69,0x98,0x10,0x81,0x3F,0x00,0x18,0x19,0x91,0xB7,0x9A, 0x28,0x8A,0x48,0x92,0xF3,0xA2,0x88,0x98,0x87,0xA1,0x88,0x80,0x81,0x95,0xD1,0xA3, 0x1B,0x1C,0x39,0x10,0xA1,0x2A,0x0B,0x7A,0x4B,0x80,0x13,0xC1,0xD1,0x2B,0x2A,0x85, 0xB2,0xA2,0x93,0xB2,0xD3,0x80,0xD1,0x18,0x08,0x08,0xB7,0x98,0x81,0x3F,0x01,0x88, 0x01,0xE2,0x00,0x9A,0x59,0x08,0x10,0xC3,0x99,0x84,0xA9,0xA5,0x91,0x91,0x91,0x80, 0xB5,0x94,0xC0,0x01,0x98,0x09,0x84,0xB0,0x80,0x7A,0x08,0x18,0x90,0xA8,0x6A,0x1C, 0x39,0x2A,0xB7,0x98,0x19,0x10,0x2A,0xA1,0x10,0xBD,0x39,0x18,0x2D,0x39,0x3F,0x10, 0x3F,0x01,0x09,0x19,0x0A,0x38,0x8C,0x40,0xB3,0xB4,0x93,0xAD,0x20,0x2B,0xD4,0x81, 0xC3,0xB0,0x39,0xA0,0x23,0xD8,0x04,0xB1,0x9B,0xA7,0x1A,0x92,0x08,0xA5,0x88,0x81, 0xE2,0x01,0xB8,0x01,0x81,0xC1,0xC7,0x90,0x92,0x80,0xA1,0x97,0xA0,0xA2,0x82,0xB8, 0x18,0x00,0x9C,0x78,0x98,0x83,0x0B,0x0B,0x32,0x7D,0x19,0x10,0xA1,0x19,0x09,0x0A, 0x78,0xA8,0x10,0x1B,0x29,0x29,0x1A,0x14,0x2F,0x88,0x4A,0x1B,0x10,0x10,0xAB,0x79, 0x0D,0x49,0x18,0xA0,0x02,0x1F,0x19,0x3A,0x2B,0x11,0x8A,0x88,0x79,0x8A,0x20,0x49, 0x9B,0x58,0x0B,0x28,0x18,0xA9,0x3A,0x7D,0x00,0x29,0x88,0x82,0x3D,0x1A,0x38,0xBA, 0x15,0x09,0xAA,0x51,0x8B,0x83,0x3C,0x8A,0x58,0x1B,0xB5,0x01,0xBB,0x50,0x19,0x99, 0x24,0xCA,0x21,0x1B,0xA2,0x87,0xA8,0xB1,0x68,0xA1,0xA6,0xA2,0xA8,0x29,0x8B,0x24, 0xB4,0xE2,0x92,0x8A,0x00,0x19,0x93,0xB5,0xB4,0xB1,0x81,0xB1,0x03,0x9A,0x82,0xA7, 0x90,0xD6,0xA0,0x80,0x1B,0x29,0x01,0xA4,0xE1,0x18,0x0A,0x2A,0x29,0x92,0xC7,0xA8, 0x81,0x19,0x89,0x30,0x10,0xE0,0x30,0xB8,0x10,0x0C,0x1A,0x79,0x1B,0xA7,0x80,0xA0, 0x00,0x0B,0x28,0x18,0xB1,0x85,0x1E,0x00,0x20,0xA9,0x18,0x18,0x1C,0x13,0xBC,0x15, 0x99,0x2E,0x12,0x00,0xE1,0x00,0x0B,0x3B,0x21,0x90,0x06,0xC9,0x2A,0x49,0x0A,0x18, 0x20,0xD1,0x3C,0x08,0x00,0x83,0xC9,0x41,0x8E,0x18,0x08,0x02,0xA0,0x09,0xA4,0x7B, 0x90,0x19,0x2A,0x10,0x2A,0xA8,0x71,0xBA,0x10,0x4A,0x0E,0x22,0xB2,0xB2,0x1B,0x8C, 0x78,0x1A,0xB5,0x93,0xA9,0x1B,0x49,0x19,0x29,0xA3,0xC6,0x88,0xAA,0x32,0x0D,0x1B, 0x22,0x08,0xC2,0x18,0xB9,0x79,0x3F,0x01,0x10,0xA9,0x84,0x1C,0x09,0x21,0xB0,0xA7, 0x0A,0x99,0x50,0x0C,0x81,0x28,0x8B,0x48,0x2E,0x00,0x08,0x99,0x38,0x5B,0x88,0x14, 0xA9,0x08,0x11,0xAA,0x72,0xC1,0xB3,0x09,0x8A,0x05,0x91,0xF2,0x81,0xA1,0x09,0x02, 0xF2,0x92,0x99,0x1A,0x49,0x80,0xC5,0x90,0x90,0x18,0x09,0x12,0xA1,0xF2,0x81,0x98, 0xC6,0x91,0xA0,0x11,0xA0,0x94,0xB4,0xF2,0x81,0x8B,0x03,0x80,0xD2,0x93,0xA8,0x88, 0x69,0xA0,0x03,0xB8,0x88,0x32,0xBC,0x97,0x80,0xB1,0x3B,0x1A,0xA6,0x00,0xD1,0x01, 0x0B,0x3B,0x30,0x9B,0x31,0x3E,0x92,0x19,0x8A,0xD3,0x5C,0x1B,0x41,0xA0,0x93,0xA2, 0xAF,0x39,0x4C,0x01,0x92,0xA8,0x81,0x3C,0x0D,0x78,0x98,0x00,0x19,0x0A,0x20,0x2D, 0x29,0x3C,0x1B,0x48,0x88,0x99,0x7A,0x2D,0x29,0x2A,0x82,0x80,0xA8,0x49,0x3E,0x19, 0x11,0x98,0x82,0x9A,0x3B,0x28,0x2F,0x20,0x4C,0x90,0x29,0x19,0x9A,0x7A,0x29,0x28, 0x98,0x88,0x33,0xCD,0x11,0x3A,0xC1,0xA4,0xA0,0xC4,0x82,0xC8,0x50,0x98,0xB2,0x21, 0xC0,0xB6,0x98,0x82,0x80,0x9C,0x23,0x00,0xF8,0x30,0xA8,0x1A,0x68,0xA8,0x86,0x9A, 0x01,0x2A,0x0A,0x97,0x91,0xC1,0x18,0x89,0x02,0x83,0xE0,0x01,0x8B,0x29,0x30,0xE2, 0x91,0x0B,0x18,0x3B,0x1C,0x11,0x28,0xAC,0x78,0x80,0x93,0x91,0xA9,0x49,0x8B,0x87, 0x90,0x99,0x3D,0x5A,0x81,0x08,0xA1,0x11,0x2F,0x1A,0x21,0x9B,0x15,0xA2,0xB0,0x11, 0xC0,0x91,0x5B,0x98,0x24,0xA2,0xF2,0x92,0x8B,0x6A,0x18,0x81,0xB5,0xB1,0x88,0x4C, 0x00,0x00,0xA4,0xC1,0x2B,0x1A,0x59,0x0A,0x02,0x80,0x1E,0x02,0x08,0xB3,0x80,0x9A, 0x23,0xB8,0xF2,0x84,0xAB,0x01,0x48,0x90,0xA7,0x90,0x0A,0x29,0x09,0x95,0x99,0xA0, 0x59,0x2B,0x00,0x97,0xB0,0x29,0x89,0x2A,0x03,0xD0,0xB7,0x1B,0x81,0x00,0xA6,0xB1, 0x90,0x09,0x48,0xC0,0x11,0x00,0x8A,0x00,0x5B,0x83,0x9A,0x18,0x2F,0x3C,0x18,0x11, 0xA9,0x04,0x1A,0x4F,0x01,0x98,0x81,0x09,0x09,0x4A,0x18,0xB4,0xA2,0x0B,0x59,0x90, 0x3B,0x49,0xBC,0x40,0x6A,0x88,0x3A,0x08,0x3E,0x3A,0x80,0x93,0xB0,0xE1,0x5A,0x00, 0xA4,0xB3,0xE3,0x90,0x0D,0x38,0x09,0x82,0xC4,0xA1,0xB1,0x4C,0x18,0x10,0x91,0xB2, 0x13,0xEA,0x34,0x99,0x88,0xA6,0x89,0x92,0x91,0xC1,0x20,0xB2,0xC2,0x86,0xD2,0xB3, 0x80,0xB2,0x08,0x09,0x87,0x91,0xC0,0x11,0x89,0x90,0x28,0xB9,0x79,0x19,0xA4,0x82, 0xD0,0x03,0x0C,0xA3,0xA5,0xB2,0xB2,0x1B,0x29,0x13,0xF1,0xB4,0x81,0x9D,0x38,0x00, 0xC4,0xA1,0x89,0x59,0x1A,0x81,0xA4,0xA9,0x1C,0x6A,0x19,0x02,0xB1,0x1A,0x4A,0x0B, 0x78,0x89,0x81,0x1C,0x2A,0x29,0x4A,0xA3,0x3E,0x1C,0x49,0x1A,0x08,0x21,0xAE,0x28, 0x4B,0x19,0x20,0x8C,0x10,0x3A,0xAB,0x26,0x8B,0x18,0x59,0x99,0x13,0xA2,0xAB,0x79, 0x2F,0x18,0x10,0xB2,0x80,0x1B,0x4D,0x5A,0x80,0x82,0x98,0x81,0x80,0x09,0xA5,0x90, 0x91,0x03,0xC2,0xE2,0x81,0xA8,0x82,0x09,0xC6,0xA3,0xB1,0x08,0x5B,0x08,0x05,0xD1, 0xA2,0x89,0x2A,0x28,0x91,0xA6,0x88,0xB0,0x49,0x80,0x09,0x08,0x88,0x07,0xB8,0x05, 0x99,0x81,0x88,0x18,0xE2,0x00,0xC3,0x18,0x0D,0x10,0x30,0xD0,0x93,0x8A,0x09,0x10, 0x2F,0x11,0x90,0xA1,0x20,0x9B,0xB1,0x73,0xC8,0x94,0x98,0x3B,0x01,0x0C,0x30,0x19, 0xF8,0x12,0x90,0xBA,0x78,0x0A,0x11,0x98,0xA0,0x79,0x8A,0x30,0x2B,0xC2,0x11,0x0D, 0x09,0x7A,0x00,0x82,0xB9,0x01,0x7A,0x89,0x21,0x09,0xA1,0x0A,0x7C,0x10,0x88,0xB5, 0x88,0x0A,0x2B,0x69,0x1A,0x10,0xA0,0x5B,0x19,0x1A,0x10,0x19,0x1A,0x6C,0x20,0x90, 0xA5,0x98,0x1B,0x0A,0x69,0x82,0xD1,0x18,0x09,0x19,0x2A,0x93,0xD4,0x9A,0x01,0x49, 0xA2,0xA2,0x82,0xD8,0x22,0xAA,0x97,0xA9,0x2D,0x38,0x2A,0xB6,0x80,0x90,0x0A,0x3C, 0x82,0x94,0xB8,0x21,0x0E,0x2A,0x22,0xB8,0x00,0x4F,0x2B,0x3A,0x81,0xA1,0x29,0x2C, 0x6A,0x13,0xD1,0xA2,0x98,0x28,0x0C,0x01,0xD5,0x08,0xA9,0x31,0xB3,0xB0,0xA7,0xB0, 0x29,0x1B,0x87,0xA2,0xA1,0xB2,0x4A,0x89,0x11,0xC3,0xF3,0x98,0x08,0x03,0xA0,0xA3, 0xC5,0x90,0xB3,0xB5,0xB4,0xB8,0x02,0x91,0x91,0xD3,0xA4,0xC1,0x1B,0x82,0x28,0xA4, 0xD1,0x94,0x8A,0x28,0x08,0x03,0xE0,0x80,0xD4,0x90,0x91,0xA1,0x3B,0x3D,0x02,0xE4, 0xA1,0x92,0x89,0x1A,0x4B,0x95,0xB3,0x90,0x99,0x6A,0x0A,0x30,0xA1,0x93,0xA6,0xA9, 0x85,0x8B,0x82,0x10,0xB1,0xA3,0x94,0xF8,0x38,0x9A,0x30,0x1A,0x8B,0xA7,0x89,0x01, 0x5B,0x19,0x18,0x11,0xF0,0x18,0x1C,0x39,0x19,0x0C,0x12,0x1C,0x2A,0x7B,0x3A,0x88, 0x2B,0x18,0x2B,0x5C,0x20,0x92,0x8D,0x38,0x8A,0x3A,0x5B,0x2E,0x3A,0x2B,0x10,0x12, 0xBB,0x6A,0x4D,0x18,0x10,0xB1,0x81,0x2A,0x8B,0x79,0x80,0x01,0x0A,0x09,0x5B,0x2D, 0x84,0x8A,0x08,0x02,0xA2,0x91,0x82,0xE8,0x50,0x9B,0x85,0xA3,0xB0,0xA3,0x1B,0x02, 0x18,0xF3,0xA2,0x88,0xAB,0x53,0xD1,0xB4,0xA3,0x09,0x09,0x18,0xD4,0x08,0xB0,0x09, 0x58,0xD1,0x82,0x89,0x81,0x1A,0x18,0x05,0xB9,0xC3,0x30,0xC0,0x95,0x80,0xC3,0x89, 0x89,0x13,0x88,0xF2,0x93,0x0E,0x18,0x01,0x92,0xA5,0xB8,0x2A,0x39,0xAA,0x33,0x9A, 0xB1,0x11,0xF5,0xA1,0xA1,0x0A,0x50,0xB8,0x03,0xC4,0xA0,0x4E,0x29,0x10,0x88,0xC2, 0x1A,0x39,0x1D,0x28,0x98,0x94,0x0E,0x10,0x2A,0x3C,0x02,0x2D,0x1B,0x4B,0x3B,0x49, 0x19,0xA9,0x48,0x2F,0x29,0x10,0x89,0x02,0x0C,0x10,0x09,0xB9,0x70,0x1B,0x8A,0x50, 0xA8,0x2B,0x49,0x89,0x69,0x88,0x95,0x89,0x90,0x92,0x4C,0x19,0x82,0xC1,0x01,0x80, 0xA0,0x2B,0x7A,0x81,0x10,0xC2,0xB7,0x98,0x88,0x19,0x2C,0x03,0xB1,0xA4,0xA1,0x0C, 0x3B,0x78,0x88,0x85,0xB1,0xA0,0x1B,0x3A,0x4A,0x08,0x94,0x81,0xF1,0x80,0x00,0x0C, 0x59,0x09,0x18,0x90,0xA6,0x92,0x8C,0x1A,0x79,0x92,0xA8,0x00,0x81,0x2E,0x2A,0x13, 0xA2,0xB0,0xA5,0x88,0x88,0x89,0x11,0x19,0xA0,0xF3,0x82,0xB0,0x83,0x5F,0x2A,0x01, 0xA1,0x94,0xB0,0x09,0x78,0x98,0xA3,0xA6,0xA0,0x91,0x80,0x93,0x98,0xC1,0x12,0x18, 0xC9,0x17,0xA0,0xA0,0x1A,0x21,0x80,0x99,0xD4,0x30,0x9D,0x00,0x10,0x2F,0x08,0x1C, 0x21,0x08,0xB4,0xC3,0x2B,0xA9,0x52,0xD2,0xA3,0xD1,0x09,0x10,0x8B,0x24,0x92,0xD1, 0x80,0x19,0xA0,0x2C,0x12,0x49,0xAA,0xB6,0x95,0xB8,0x08,0x3A,0x2B,0x01,0xF3,0xB3, 0x0B,0x09,0x79,0x18,0xA2,0xA4,0xA0,0x18,0x0C,0x20,0x08,0xA9,0x16,0x0C,0x00,0x1B, 0x08,0x2B,0x7B,0x01,0x01,0xB9,0x59,0x19,0x8B,0x45,0xA8,0x80,0x0C,0x1A,0x41,0x1E, 0x00,0x28,0xA8,0x5A,0x00,0xC1,0x49,0x99,0x21,0x1D,0x08,0x85,0x99,0x95,0x89,0x90, 0x11,0x90,0xD1,0x28,0xB2,0xA7,0x99,0x81,0x02,0xAC,0x13,0x81,0xB2,0xA6,0xA9,0x28, 0x1C,0xB1,0x33,0xD1,0xC1,0x58,0xA8,0x14,0xB0,0xB7,0x91,0xA0,0x82,0x89,0xC2,0x28, 0xA1,0xB2,0x49,0xD2,0x94,0xC8,0x12,0x80,0x99,0x85,0x08,0xD3,0x09,0xA2,0xB3,0x1E, 0x08,0x21,0xB9,0x23,0xB4,0xAB,0x41,0xAC,0x87,0x09,0xA2,0xC5,0x0B,0x2A,0x5A,0x91, 0x20,0x9A,0x89,0x78,0x9B,0x31,0x89,0x80,0x29,0x0A,0xB7,0x3C,0x98,0x48,0x1D,0x00, 0x01,0xB0,0x20,0x2F,0x29,0x4A,0x89,0x94,0x1C,0x88,0x28,0x2B,0x10,0x88,0x9A,0x71, 0x9A,0x08,0x4A,0x2F,0x18,0x2B,0x18,0x02,0xA8,0x4B,0x7A,0x99,0x48,0x80,0xA8,0x20, 0x1D,0x40,0xA8,0x10,0x08,0xA8,0xC5,0x88,0xC2,0x18,0x88,0x2A,0x12,0xF3,0x82,0xD8, 0x20,0x0A,0x09,0xA6,0x98,0x04,0xB9,0x11,0x18,0xC3,0xE1,0x29,0xA1,0x11,0xC1,0x03, 0xE2,0x9A,0x33,0xA9,0xB5,0x98,0x92,0xA1,0x02,0xF8,0x21,0xA8,0x10,0x02,0xC1,0xB7, 0x1B,0x90,0x5B,0x3C,0x83,0x93,0xE0,0x19,0x1A,0x11,0x11,0xF1,0x92,0x89,0x19,0x2C, 0x2C,0x41,0x99,0x92,0x90,0x3F,0x18,0x4B,0x00,0x08,0xD2,0x01,0xB2,0xAA,0x78,0x09, 0x01,0x91,0xA2,0x98,0x2F,0x3A,0x2C,0x01,0x00,0x93,0xE0,0x28,0x2C,0x2B,0x01,0x12, 0xE1,0x80,0xB3,0x3D,0x3A,0x0A,0x50,0x98,0xC2,0xA0,0x11,0xAA,0x30,0x87,0x90,0xC2, 0x29,0x88,0x38,0xC8,0xB5,0x90,0xBA,0x70,0x1A,0x02,0x94,0xD0,0x80,0x1A,0x82,0xA6, 0xB0,0x91,0x18,0xB3,0x00,0x13,0xF1,0xA2,0xC1,0x82,0xB0,0x00,0x15,0x0B,0xD3,0x02, 0xA8,0x91,0x2B,0x1F,0x49,0x88,0xA6,0x80,0x88,0x08,0x1B,0xA5,0x80,0xB9,0x06,0x0B, 0x90,0x21,0x9D,0x48,0x18,0xA0,0x15,0xC9,0x82,0x2B,0x1A,0x42,0x9A,0xC4,0x39,0xBC, 0x69,0x00,0xA0,0x29,0x8C,0x39,0x59,0x08,0x09,0x49,0xA9,0x6B,0x81,0x00,0x98,0xB0, 0x68,0x3D,0x81,0x88,0x18,0x19,0x1D,0x12,0x80,0xB2,0x3A,0x3F,0x85,0x92,0xD0,0x00, 0x0A,0x19,0x12,0xF1,0x02,0x9B,0x19,0x40,0xB9,0x11,0x02,0xF2,0x1A,0x08,0x94,0x0A, 0xC2,0x83,0x0B,0xB4,0xA4,0xC0,0x32,0xD8,0x86,0x98,0x90,0x95,0x89,0xA3,0x83,0xC2, 0x92,0xE1,0x92,0x82,0xD9,0x03,0x08,0xA9,0x85,0x92,0xA2,0x80,0xE0,0x30,0x8B,0xB3, 0x87,0x89,0x90,0x83,0xA0,0x08,0x92,0x93,0x3E,0xAB,0x43,0x89,0xE3,0x80,0x83,0x2F, 0x00,0xA3,0x80,0xC9,0x22,0x3F,0x08,0x81,0x0B,0x33,0x9A,0xA3,0x7B,0x0C,0x29,0x4A, 0x1B,0x21,0xAA,0x70,0x1B,0x0D,0x48,0x1A,0x81,0x88,0xB1,0x39,0x3F,0x08,0x58,0xA0, 0x81,0x1A,0x1A,0x2B,0x6D,0x11,0x0A,0x91,0x01,0x1A,0x98,0x5A,0x0C,0x03,0xB1,0x84, 0xA3,0xAD,0x58,0x2A,0xA1,0x84,0xB1,0xA0,0x5C,0x2B,0x13,0xA8,0x95,0x83,0xE8,0x10, 0x81,0xB0,0x00,0xC2,0x96,0xA0,0x91,0x00,0x2C,0x90,0x30,0xF2,0x80,0xA8,0x39,0x21, 0xC1,0x03,0xAC,0x39,0x7C,0x29,0x91,0x1A,0x00,0x19,0x2C,0x3A,0x93,0xB0,0x29,0x8F, 0x28,0x02,0x93,0xF3,0xA9,0x01,0x03,0xE0,0x08,0x09,0x1D,0x58,0xA1,0x83,0xA9,0x6B, 0x2A,0x3C,0x21,0x89,0xC2,0x2C,0x4B,0x8A,0x50,0x81,0x98,0xA8,0x32,0x0C,0x8E,0x24, 0x0B,0x1A,0x81,0x92,0xA1,0x4F,0x18,0x3A,0x0A,0xB4,0x18,0x2E,0x39,0x82,0x19,0xD3, 0xD0,0x28,0x1B,0x11,0x98,0x07,0xAA,0x28,0x00,0x88,0xB4,0x89,0x1B,0x1F,0x22,0x00, 0xB3,0xC9,0x33,0xAB,0x2B,0xB5,0x48,0x98,0x98,0xA7,0x10,0xD2,0xC1,0x23,0xCA,0x93, 0xC6,0x80,0xA1,0x88,0x02,0x89,0xE2,0x09,0x38,0xBA,0x40,0x89,0x21,0xD8,0x49,0x10, 0x8D,0x02,0x90,0xC3,0x9A,0x24,0x89,0x08,0x84,0xA5,0x9C,0x10,0x11,0x9C,0x88,0x30, 0x3C,0xA1,0x94,0x58,0x8C,0x0B,0x69,0x29,0x9A,0x81,0x12,0x2B,0x8B,0x79,0x94,0xB0, 0xC1,0x84,0xC2,0x99,0x25,0x99,0x11,0xA2,0x93,0xE4,0x99,0x80,0x0A,0x00,0x10,0xB7, 0xB0,0x31,0xBA,0x3C,0x21,0xB3,0xF1,0x18,0xA0,0x2A,0x20,0xA3,0x06,0xE8,0x28,0xA1, 0xB4,0x08,0x0B,0x11,0x4B,0xB7,0x90,0xA5,0x98,0x3D,0x19,0x02,0xA1,0xC4,0xB2,0x19, 0x28,0xC0,0xA5,0x92,0xB1,0xA3,0x0A,0x0A,0x08,0x2B,0x70,0xC4,0xB3,0x00,0xBC,0x4B, 0x39,0x12,0xE3,0xA0,0x00,0x3F,0x18,0x29,0x94,0xD1,0x19,0x09,0x00,0xA1,0x83,0x99, 0x9B,0x35,0x80,0xC4,0xB1,0x6A,0x1A,0x1C,0x29,0x38,0x0E,0x19,0x5A,0x1A,0x82,0x8A, 0x59,0x2A,0x2E,0x20,0x88,0xA8,0x3A,0x38,0x3D,0x00,0xB3,0x29,0xAD,0x49,0x10,0x0C, 0x01,0x01,0xA3,0x8F,0x85,0x09,0x1B,0x88,0x10,0xA3,0xD2,0x90,0x3C,0x5C,0x39,0x03, 0xD1,0xA0,0x00,0x2A,0x0B,0x04,0xA7,0x90,0xA0,0x11,0x90,0x99,0x83,0xB4,0xB1,0xF1, 0x84,0x88,0x90,0x18,0x18,0xD3,0xD2,0xB3,0xA0,0x1A,0x21,0xA7,0xB2,0xB3,0x92,0x9A, 0x22,0xB9,0x28,0x38,0xBD,0x87,0x2A,0xB1,0x13,0x0D,0x0A,0x38,0xC9,0x24,0xC0,0x19, 0x23,0x0F,0x01,0x88,0xC0,0x2A,0x82,0x18,0x28,0xF0,0x18,0x2A,0x29,0x4B,0x35,0xB8, 0xA3,0x9D,0x18,0x1B,0x40,0x00,0x9A,0x5C,0x3A,0x09,0x2F,0x38,0x8A,0x3B,0x3B,0x11, 0x5C,0x19,0x2B,0x4A,0x08,0x0A,0x3D,0x20,0x4F,0x3A,0x19,0x2A,0x18,0x4D,0x1B,0x3A, 0x11,0x0D,0x3A,0x3C,0x4B,0x93,0x81,0xAA,0x6B,0x4A,0x18,0x00,0xC3,0xC3,0x9A,0x59, 0x2A,0x1B,0xA7,0xA1,0x81,0x88,0x88,0x58,0xB2,0xB1,0x2B,0x83,0xD4,0x81,0x08,0x0F, 0x00,0x20,0xC2,0xE2,0x80,0x08,0x1C,0x29,0x04,0xB1,0xA2,0x01,0x1C,0x91,0x00,0x0C, 0x49,0xB0,0x43,0xF2,0x99,0x39,0x3F,0x00,0x81,0x94,0xC1,0x09,0x1A,0x69,0x90,0x80, 0x94,0xAA,0x20,0x2A,0x91,0xB1,0x39,0x7A,0x38,0xD1,0x10,0x8A,0x8C,0x5A,0x01,0xB5, 0x98,0x80,0x2A,0x0B,0x32,0x92,0xF1,0x81,0x9A,0x23,0x8A,0xA3,0xB7,0x09,0x03,0x08, 0xD0,0x94,0x9A,0x09,0x01,0x93,0xB7,0xC2,0x8C,0x3A,0x83,0x99,0x05,0xA0,0x0B,0x29, 0x93,0xE5,0x80,0x89,0x38,0x90,0x8A,0xD7,0xA1,0x19,0x1B,0x48,0x98,0x92,0xC3,0xA1, 0x09,0x3F,0x02,0x0C,0x22,0xC3,0xB2,0xA1,0x01,0x9F,0x4A,0x01,0xA3,0xD3,0xB0,0x28, 0x3F,0x29,0x20,0xA2,0xC2,0xB1,0x08,0x5A,0x98,0x13,0xD2,0xC1,0x01,0xB2,0x80,0x3D, 0x03,0xC1,0x89,0x96,0x90,0x90,0x3A,0x1A,0x9A,0x32,0xB6,0xA2,0x8E,0x4A,0x28,0x8A, 0x84,0xA2,0x8A,0x2D,0x49,0x09,0x88,0x18,0x30,0x9D,0x2C,0x23,0xB1,0x0C,0x92,0x2D, 0x39,0x82,0xC4,0x2E,0x10,0x1A,0x10,0xB9,0x48,0x19,0x39,0xBA,0x34,0xDA,0x2D,0x48, 0x1A,0xA6,0x98,0x83,0x9A,0x1D,0x38,0x04,0xD0,0x18,0x90,0x2C,0x11,0x93,0xD3,0x9A, 0x11,0x08,0x82,0xF1,0x01,0xA0,0x2A,0x93,0xD3,0xB4,0xB8,0x82,0x2F,0x11,0xA3,0xB3, 0xA8,0x3B,0x09,0x23,0x96,0xC8,0x3B,0x3F,0x93,0x82,0xA1,0x90,0x3F,0x28,0x81,0xD1, 0x93,0x08,0x2D,0x18,0x91,0xB3,0xB5,0x98,0x2A,0x2B,0x84,0xB1,0x5B,0x8A,0x31,0x18, 0x80,0x8B,0x7E,0x39,0x2B,0x02,0xC1,0x8B,0x6C,0x49,0x09,0x10,0xA1,0x08,0x01,0x0C, 0x20,0xA1,0x09,0x4F,0x18,0x00,0x01,0xA0,0x5C,0x1B,0x5B,0x10,0x92,0x90,0x2B,0x5A, 0x3D,0x18,0x91,0x19,0x98,0x2D,0x39,0x89,0x2D,0x3A,0x48,0x2C,0x11,0xB5,0x9A,0x19, 0x5B,0x28,0x90,0x95,0x98,0x89,0x2B,0x40,0x08,0x90,0xF3,0x0A,0x08,0xA6,0x80,0x91, 0xB2,0xA0,0x02,0xF2,0xA1,0xB7,0x89,0x81,0x82,0x91,0xB1,0x21,0xAB,0x32,0xE9,0x04, 0xA2,0x8D,0x12,0x91,0xA3,0xA3,0xD2,0x8B,0x39,0xD1,0x84,0xE2,0x90,0x00,0x2B,0x29, 0xA3,0xD4,0xA1,0x91,0x1D,0x5A,0x08,0x19,0x11,0x99,0x08,0x18,0x49,0x0F,0x18,0x10, 0x82,0xF1,0x00,0x89,0x2F,0x3A,0x01,0xB3,0xC2,0x81,0x3F,0x29,0x08,0x10,0xA1,0xA1, 0x3B,0x5D,0x19,0x28,0x0B,0x38,0x82,0x91,0x19,0xBD,0x3B,0x7A,0x80,0x12,0xB3,0xE0, 0x0B,0x6A,0x01,0x88,0xA4,0x08,0x0B,0x08,0x59,0x80,0x80,0x1D,0x49,0x89,0x00,0x84, 0x99,0x1A,0x2B,0x32,0xE3,0xB4,0xA9,0x3A,0x99,0x31,0xE3,0xAA,0x58,0x3B,0x88,0x95, 0xC0,0x18,0x4A,0x09,0x30,0xF2,0xA3,0x1C,0x1B,0x49,0x00,0xD3,0xB2,0xA0,0x18,0x11, 0x92,0xD3,0xB2,0x91,0x80,0xE7,0xA1,0x91,0x98,0x19,0x22,0xC2,0xD2,0x18,0x8D,0x3B, 0x10,0xA5,0x91,0x98,0x02,0x3E,0x80,0x01,0x90,0xAA,0x13,0xF1,0x02,0xD1,0x08,0x19, 0x49,0xB4,0x91,0xB4,0x99,0x2A,0x0C,0x32,0xC0,0x05,0x88,0x0B,0x80,0x2C,0x81,0x10, 0x0B,0x51,0xA9,0x19,0x05,0xBF,0x28,0x20,0xE1,0x90,0x80,0x28,0x19,0x08,0x26,0xB1, 0xA1,0x18,0x88,0x2A,0xF0,0x12,0x8A,0xB3,0x14,0x1B,0xD4,0xD8,0x10,0x08,0x8A,0x17, 0xA0,0x98,0x2B,0x3A,0x29,0x48,0xA4,0x99,0x0E,0x4A,0x12,0x8B,0x31,0x8B,0x4E,0x1A, 0x11,0xB5,0x89,0x91,0x29,0x89,0xC2,0x97,0x90,0x0A,0x19,0x11,0x91,0xC1,0xD5,0x08, 0x89,0x20,0x91,0xB1,0x1A,0x2D,0x18,0x29,0xD2,0x3B,0x3E,0x3A,0x2A,0x90,0x82,0x1C, 0x49,0x3B,0x93,0xB6,0xC8,0x4C,0x02,0x91,0x93,0xF2,0x88,0x2D,0x28,0x81,0x82,0xC1, 0x89,0x2D,0x6B,0x19,0x82,0x80,0x18,0x8B,0x39,0x39,0xC8,0x3A,0x6A,0x0A,0x22,0xD2, 0x09,0x2C,0x1A,0x68,0x92,0xE2,0x89,0x2A,0x2A,0x30,0xC2,0xA3,0xB4,0x1D,0x2A,0x09, 0x93,0x18,0xF2,0x89,0x28,0xB3,0x01,0x8F,0x18,0x11,0xA1,0x93,0x90,0xD1,0x7A,0x20, 0xC3,0xA2,0xA8,0x88,0x1D,0x28,0xA5,0xA2,0xA2,0x0B,0x29,0x2B,0x87,0xC1,0x80,0x0A, 0x19,0x01,0x12,0xF1,0x10,0x80,0x0A,0x18,0x08,0x2F,0x4A,0x02,0x89,0x1B,0x29,0x5D, 0x4C,0x08,0x82,0xA1,0x0A,0x3A,0x4B,0x29,0xC6,0xC3,0x09,0x09,0x88,0x39,0x98,0x82, 0xA5,0x1A,0x30,0x11,0xBD,0x3F,0x12,0x8B,0x28,0xC3,0x88,0x3F,0x2B,0x3B,0x48,0xA1, 0x80,0x8A,0x4D,0x39,0x01,0x93,0xA2,0xF1,0x19,0x19,0x0A,0x02,0xB2,0x8B,0x24,0xD2, 0x4B,0x12,0xC8,0x2E,0x10,0xB5,0x89,0x01,0x09,0x1C,0x2A,0x03,0xD4,0x91,0x98,0x99, 0x11,0x2B,0xE4,0x00,0x00,0x01,0xE0,0xA5,0x89,0x99,0x31,0x18,0xD0,0xB7,0x98,0x18, 0x0A,0x10,0x94,0xC2,0x90,0x18,0x00,0x99,0x87,0xA0,0x90,0x2A,0x3C,0x02,0xB8,0xC1, 0x79,0x1A,0x20,0x08,0xA1,0xD2,0x1C,0x29,0x03,0xD1,0x29,0x99,0x2C,0x50,0xB3,0xD1, 0x08,0x09,0x3C,0x10,0x04,0xB2,0x0D,0x2B,0x59,0x80,0x90,0x01,0x0F,0x3A,0x18,0x01, 0xA2,0x9B,0x5B,0x3D,0x81,0x03,0xD2,0x98,0x59,0x90,0x81,0x92,0xB4,0x8B,0x1B,0x40, 0xB2,0xB5,0x08,0x4B,0x01,0x09,0xD1,0x91,0x8B,0x7A,0x10,0xB3,0xC3,0x99,0x49,0x1A, 0x29,0xB5,0xA2,0xAB,0x40,0x81,0x19,0xB7,0xB0,0x20,0x2B,0xD4,0x88,0xA1,0x91,0x3C, 0x82,0x37,0xD3,0xB1,0x8A,0x1B,0x30,0xB3,0xF4,0xA1,0x91,0x09,0x10,0x03,0xD0,0x83, 0xA9,0x8F,0x10,0x01,0x90,0x18,0x80,0x20,0x2B,0xF1,0x28,0x99,0x2A,0x41,0xF0,0x12, 0xAA,0x83,0x82,0xD1,0xC1,0x08,0x89,0x59,0x09,0x83,0x87,0xB0,0x2A,0x4D,0x18,0x09, 0x19,0xB3,0x4B,0x3F,0x39,0x19,0x09,0x01,0x89,0x03,0x1F,0x00,0x1A,0x0B,0x10,0x68, 0xA0,0x18,0x8C,0x6A,0x09,0x08,0x97,0xA1,0x81,0x1B,0x2B,0x4C,0x03,0xB4,0xA8,0x92, 0x4B,0x3C,0xA1,0x81,0x95,0xA8,0x81,0x12,0xBB,0x92,0x45,0xB9,0x93,0xF4,0x88,0x0A, 0x2D,0x28,0x00,0xA3,0xA3,0x8A,0x3F,0x48,0xB1,0x92,0xB4,0xA8,0x30,0x80,0xD3,0x80, 0xD1,0x19,0x3B,0xC4,0x81,0xC1,0x29,0x0D,0x20,0x13,0xC8,0xB4,0x4C,0x09,0x00,0x82, 0xC2,0x3B,0x0D,0x30,0x0B,0x12,0xF0,0x1B,0x20,0x0A,0xA6,0x80,0x0A,0x4A,0x4A,0x80, 0x94,0xB1,0x2E,0x3B,0x1A,0x10,0x93,0x10,0x4C,0x3D,0x08,0x82,0xC9,0x19,0x6A,0x2B, 0x38,0xD1,0x08,0x19,0x2A,0x5A,0x82,0xB1,0x8D,0x29,0x78,0x09,0x82,0x0A,0x2C,0x1B, 0x19,0x41,0xB8,0x8C,0x79,0x2B,0x11,0x88,0x82,0x91,0xDC,0x28,0x11,0xB0,0x11,0x18, 0xC9,0x62,0xA1,0x91,0x98,0x3B,0x3A,0xB0,0xF4,0x01,0xC0,0x29,0x39,0xF8,0x95,0x91, 0x88,0x88,0x91,0x03,0xA1,0xE2,0x18,0x82,0xD1,0xA2,0xD1,0x80,0x19,0x20,0x83,0xB1, 0xE3,0x80,0x91,0x4D,0x1A,0x03,0xB2,0x09,0x18,0xD1,0x19,0x09,0x92,0xA6,0xA0,0xB6, 0xB2,0x8B,0x38,0x10,0x42,0xD3,0xD0,0xA8,0x20,0x2C,0x10,0x01,0xB1,0xB4,0xAB,0x5B, 0x79,0x80,0x10,0x1A,0xA8,0x3D,0x18,0x20,0xB3,0x8F,0x18,0x01,0x00,0x09,0xF3,0x89, 0x69,0x88,0x81,0x91,0x08,0xE1,0x1A,0x08,0x11,0x81,0x1E,0x29,0xA0,0x01,0x00,0x90, 0x3E,0x7B,0x18,0x82,0xC3,0xA1,0x2A,0x2C,0x5B,0x81,0xA5,0x90,0x81,0x00,0x0B,0x1A, 0x1C,0x2C,0x32,0xC0,0xF3,0x80,0x2D,0x2A,0x10,0x02,0xE4,0xC1,0x89,0x4A,0x09,0x01, 0x03,0xD2,0x98,0x2A,0x39,0x8A,0x89,0x26,0xB1,0xB2,0x12,0xC0,0x0A,0x5A,0x18,0x98, 0xF3,0x92,0x99,0x99,0x79,0x01,0xB5,0xA1,0x80,0x80,0x90,0x83,0xA0,0xE2,0x81,0x29, 0x93,0x8A,0x0A,0x6A,0x1F,0x18,0x02,0xC8,0x01,0x19,0x3B,0x4A,0x98,0x17,0xA8,0x0D, 0x38,0xA1,0x91,0x10,0xA2,0x2B,0x4C,0xA6,0x81,0xBA,0x21,0x4C,0x80,0x21,0xD1,0x92, 0x2C,0x08,0x30,0x9F,0x93,0x2A,0x89,0x03,0x8B,0x87,0x0A,0x0D,0x12,0x98,0xA4,0x93, 0xBB,0x59,0x18,0xA1,0x32,0xE9,0x84,0x08,0x8A,0x02,0xA1,0x91,0x4B,0xB4,0x20,0x88, 0xF0,0x3A,0x1A,0x88,0x87,0xB1,0x92,0x0A,0x08,0x6B,0x83,0xC3,0x91,0xC0,0x2B,0x79, 0x08,0x8A,0x84,0xA0,0x89,0x40,0x1B,0xA1,0x39,0x98,0x17,0xC2,0xA2,0x12,0xCD,0x20, 0x89,0x92,0x25,0xB0,0x2D,0x3A,0x8B,0x58,0x2A,0xA0,0x4C,0x08,0x30,0xAE,0x82,0x59, 0x89,0x1A,0x10,0xC2,0x18,0x2C,0x40,0x1E,0x01,0xA3,0x8A,0x81,0x2C,0x29,0x29,0xA9, 0x13,0x51,0xAD,0x12,0x89,0x8F,0x18,0x2C,0x39,0x00,0xC1,0x10,0x3C,0x2A,0x41,0xC8, 0xA2,0x91,0x0A,0x6C,0x10,0x12,0x88,0xE8,0x30,0x91,0x81,0xD8,0x01,0x1B,0x0D,0x07, 0x00,0xA8,0x92,0x0A,0x28,0xD2,0xC3,0x02,0xAA,0x94,0x81,0xB4,0xB3,0x1A,0x0B,0x13, 0xF9,0x16,0xA1,0x8A,0x59,0x19,0x02,0xC1,0x91,0x8B,0x3D,0x18,0x3B,0xA4,0x94,0x80, 0x99,0x88,0x1C,0x79,0x0A,0x02,0x03,0xF8,0x90,0x39,0x5B,0x19,0x02,0xC3,0x90,0xBB, 0x58,0x6A,0x09,0x02,0x89,0x91,0x88,0x1A,0x69,0x8A,0x19,0x15,0xA0,0xA2,0x00,0x9A, 0x6B,0x49,0x88,0xA3,0x92,0xBB,0x6B,0x3D,0x38,0x01,0x98,0x91,0x3F,0x09,0x18,0x20, 0x90,0x80,0xAC,0x70,0x91,0x9B,0x51,0x09,0x88,0x99,0x14,0x8B,0x98,0x83,0x79,0xA0, 0x99,0x13,0x01,0x19,0xE0,0x83,0x0B,0xB0,0x0C,0x31,0x95,0xB5,0xC2,0x8A,0x39,0x20, 0x80,0x39,0xF3,0xB1,0x10,0x88,0x5E,0x18,0x94,0xA1,0x88,0xA1,0x98,0x15,0xAA,0x39, 0xD4,0x84,0xC0,0xA2,0xA2,0x0C,0x81,0x86,0xB5,0xA1,0xB1,0x14,0x1B,0xB1,0x02,0x92, 0xC3,0xE0,0x88,0x11,0xAA,0x69,0x18,0x81,0xA3,0xB0,0x01,0xBF,0x2A,0x31,0x93,0xF1, 0x00,0x89,0x18,0x19,0x11,0xD3,0xE0,0x10,0x18,0xB1,0x18,0x24,0x9A,0x2B,0xA4,0xC0, 0xB0,0x31,0x6C,0x19,0xB4,0x12,0xA8,0xEA,0x58,0x10,0x8B,0x93,0x82,0x88,0x9A,0x41, 0x10,0xC3,0xEA,0x41,0xA9,0x9C,0x34,0xA1,0x2A,0x79,0xA2,0x01,0xA8,0xB3,0x28,0xCC, 0x41,0x9A,0xB3,0x4B,0xB3,0x27,0x8B,0x83,0x2B,0x2F,0x08,0x28,0xB2,0x80,0x2C,0x30, 0x5E,0x09,0x12,0x9B,0x09,0x22,0x5B,0x19,0x8A,0x11,0x59,0x99,0xA4,0x32,0xCD,0x18, 0x08,0x10,0x85,0xB3,0xB4,0x1E,0x88,0x28,0x8A,0x11,0x09,0xC0,0x79,0x80,0x91,0x3B, 0x80,0x10,0x0F,0x01,0x80,0x91,0x19,0x3D,0x92,0x28,0xA8,0x37,0x9A,0x0A,0x3A,0x8A, 0x45,0xA9,0xA4,0x00,0xAA,0x09,0x3D,0x59,0x20,0xE1,0x08,0x98,0x90,0x59,0x10,0x09, 0xA3,0xC3,0x93,0x99,0x2B,0x69,0x11,0xD1,0xB1,0xA4,0x91,0x3C,0x89,0x83,0xF0,0x10, 0x91,0xA1,0x89,0x59,0x05,0x99,0x93,0x94,0xC8,0x08,0x0A,0x09,0x17,0xB1,0x83,0xC1, 0x91,0x40,0xA2,0xC2,0x98,0xC3,0xBA,0x28,0x23,0x0F,0x80,0x50,0xB8,0x19,0x10,0x96, 0x98,0x8C,0x05,0x98,0x19,0x29,0x2B,0x3B,0x0A,0xE2,0x01,0x0F,0x3C,0x38,0x08,0x09, 0x81,0x4A,0x6C,0x08,0x00,0x88,0x98,0x38,0x2C,0x5A,0x1B,0x20,0x1A,0x39,0xB0,0x09, 0xCB,0x5B,0x49,0x09,0x71,0x00,0xC1,0x0E,0x08,0x38,0x0C,0x02,0x10,0x0E,0x10,0x8A, 0x48,0x19,0x90,0x92,0x0D,0xA3,0x98,0x3B,0x79,0x19,0x01,0x10,0xE1,0x80,0x19,0x2B, 0x10,0xF2,0x02,0xAB,0x84,0x9A,0x29,0xB4,0x80,0x92,0x03,0x88,0x95,0xD0,0x03,0x90, 0xA0,0xC7,0xA1,0xB0,0xA2,0x02,0x18,0xB5,0xD4,0x01,0xC0,0x08,0xA2,0x93,0xA8,0xA0, 0xC3,0x20,0xF3,0x90,0x00,0xD5,0x08,0x89,0xA5,0x80,0xA0,0x81,0x82,0xC2,0x09,0xD1, 0x13,0xCB,0x03,0x84,0x91,0xE1,0x1B,0x12,0x08,0xAB,0x87,0x18,0xAB,0x58,0x89,0x28, 0x81,0xC9,0x33,0xA9,0x80,0x2E,0x20,0x83,0xB9,0x20,0x3B,0x9E,0x7A,0x08,0x81,0x18, 0x0B,0x88,0x79,0x80,0x8B,0x00,0x12,0x0E,0x89,0x51,0x1B,0x81,0xA0,0x3A,0x01,0xAF, 0x11,0x28,0xBA,0x35,0x98,0x88,0x52,0xC0,0x83,0x2F,0xA9,0x11,0x0A,0x19,0x25,0xD0, 0x30,0x9C,0x08,0x21,0x98,0x81,0x2A,0xF3,0x2A,0x80,0xB6,0x2B,0x08,0x93,0xE9,0x02, 0x81,0x8C,0x21,0x00,0xA6,0xA9,0x94,0x01,0x8F,0x80,0x94,0x98,0x93,0xB4,0x00,0x08, 0xC0,0x14,0x98,0xB3,0xB4,0xC1,0x09,0x18,0xA7,0x00,0xA3,0xC8,0x0A,0x3C,0x19,0x96, 0x83,0xC1,0x99,0x19,0x4A,0x85,0x80,0xC1,0x91,0x99,0x90,0x2A,0x17,0x95,0x99,0x88, 0x12,0xAE,0x39,0x08,0x92,0x84,0xB0,0xA8,0x79,0x09,0x19,0x01,0xB2,0xA3,0x8F,0x28, 0x2B,0xA2,0x40,0x82,0xA0,0x4C,0xA9,0x39,0x8D,0x81,0x70,0x88,0xA0,0x1A,0x49,0x2D, 0x1A,0x26,0xA8,0x98,0x08,0x29,0x0B,0x12,0x96,0xB1,0xB2,0x3A,0x13,0x9B,0x60,0xA0, 0x88,0xB2,0x34,0xEA,0x1A,0x2A,0x79,0x98,0x10,0x04,0x8C,0x1C,0x81,0x04,0x8C,0x83, 0x19,0x2F,0x81,0x93,0x98,0x10,0x08,0x30,0x2A,0xFA,0x05,0x08,0x2A,0x89,0x91,0xA3, 0xFA,0x11,0x11,0x00,0x8C,0x04,0x8A,0x2A,0xB5,0x10,0xA9,0xC2,0x3D,0x1B,0x32,0x04, 0x0A,0x1A,0x09,0x40,0x1F,0x92,0x1D,0x2A,0x91,0x10,0x30,0x2F,0x0B,0x68,0x99,0xA2, 0x92,0x88,0x78,0xA9,0x20,0x28,0xE2,0x92,0x1A,0x99,0x4B,0x19,0x22,0xA1,0xE2,0x21, 0x2F,0x98,0x29,0x18,0x91,0x08,0xB0,0x79,0x1A,0x82,0x3B,0xB1,0xA7,0x8A,0xB3,0x98, 0x5B,0x23,0xCA,0x42,0x83,0xF0,0x90,0x18,0x98,0x08,0xB4,0x20,0xA3,0xC0,0x43,0xD8, 0x80,0x81,0xA3,0x99,0xD9,0xA7,0x19,0x90,0x10,0x05,0xB1,0x8B,0x02,0xA4,0xBD,0x23, 0x93,0x8A,0x99,0x4B,0x03,0xC1,0xF8,0x38,0x09,0x2B,0x14,0xD0,0x03,0x8A,0x2A,0x39, 0xB9,0x97,0x90,0xAA,0x50,0x01,0x99,0x51,0xD1,0x09,0x1A,0xB5,0x00,0x8B,0x93,0x08, 0x98,0x11,0xF9,0x85,0x2B,0x08,0x96,0x89,0x90,0x2A,0x12,0x4A,0xD8,0x85,0x2B,0x0E, 0x10,0x00,0x01,0xB1,0x9B,0x69,0x1A,0x90,0x40,0xB8,0x01,0x08,0x0A,0x2C,0x09,0x14, 0x4B,0xE2,0x82,0x88,0xB1,0x78,0x0A,0x01,0xC2,0x93,0x19,0xCE,0x20,0x3C,0x82,0xB4, 0x1B,0x20,0x8C,0x3B,0x29,0xAB,0x86,0x23,0xD8,0x81,0x9A,0x5A,0x49,0xB0,0x16,0xA0, 0xB0,0x28,0x1B,0x13,0x93,0xE4,0xA2,0xA9,0x08,0x5A,0xB3,0x12,0xC1,0xE1,0x10,0x88, 0x01,0x0C,0x92,0x08,0x89,0xB7,0x88,0x81,0x10,0x9A,0x17,0xA0,0xB0,0x13,0x99,0xE0, 0x39,0x31,0xD2,0xB2,0x80,0x0B,0x2D,0x49,0x80,0x01,0xB0,0x06,0x09,0x0C,0x3A,0x69, 0xA0,0x08,0xB2,0xA1,0x69,0x2B,0x5A,0x81,0x92,0xBA,0x21,0xB1,0x7D,0x10,0x80,0x08, 0x88,0x82,0x32,0x0D,0xB0,0x1A,0x1C,0x21,0x94,0xA9,0x58,0xB9,0x5A,0x4A,0xA0,0x13, 0xA9,0x80,0x7C,0x00,0x20,0x8A,0x04,0x0C,0x00,0x82,0x2A,0xB2,0xAC,0x4B,0x69,0xA0, 0xA6,0x81,0x9B,0x19,0x38,0x8B,0x17,0xB2,0x81,0x2A,0xBB,0x94,0x29,0xA2,0x15,0xBA, 0x97,0xA3,0xB9,0x79,0x01,0xB2,0x02,0xF1,0x90,0x0A,0x29,0x11,0x88,0xE5,0xA0,0x81, 0x19,0x91,0x90,0x28,0xB3,0x14,0xD0,0xB5,0x91,0x9A,0x29,0x0B,0x07,0xA2,0xB3,0x01, 0x9D,0x28,0x41,0xD0,0x91,0x90,0x82,0x1A,0xA8,0x44,0x9A,0xA9,0x21,0xE3,0xA9,0x4B, 0x19,0x78,0x89,0x83,0xA3,0xB9,0x5A,0x3D,0x80,0x82,0xA2,0xA0,0x6C,0x10,0x20,0x8B, 0x93,0x8B,0x0E,0x33,0xA9,0xB1,0x68,0x8A,0x31,0xAC,0x94,0xB4,0x8B,0x32,0x0B,0xB4, 0x81,0x91,0x1D,0x33,0xD9,0x31,0xE1,0x8B,0x3B,0x30,0x12,0x49,0xD2,0x8E,0x29,0x18, 0x8A,0x92,0x02,0xAA,0x59,0x1C,0x32,0x88,0x01,0x23,0xFB,0x83,0x29,0xDA,0x59,0x01, 0x81,0x92,0xE1,0x18,0x8A,0x1D,0x30,0x93,0xF1,0x00,0x01,0x0B,0x39,0x92,0x89,0xA0, 0x11,0x5B,0xE0,0x82,0x09,0x13,0xAA,0xB4,0x16,0xD8,0x91,0x2A,0x29,0x84,0x1B,0xC5, 0x98,0x98,0x31,0x98,0x99,0x17,0xA9,0x20,0x92,0xC3,0x18,0x9D,0x20,0x3D,0x89,0x94, 0xA2,0x1C,0x5C,0x29,0x39,0xA0,0xB3,0x00,0x0C,0x4C,0x48,0x92,0x0A,0x91,0x85,0x9A, 0x01,0x82,0x1F,0x10,0x99,0x15,0xC1,0xA0,0x39,0x1A,0x1D,0x85,0xB4,0x90,0x1A,0x2A, 0x4B,0x01,0xB2,0x93,0xBE,0x12,0x83,0xC9,0x18,0x09,0x20,0x78,0xF1,0x08,0x19,0x88, 0x3A,0x83,0xB3,0xA9,0x93,0x7A,0x0A,0x96,0x98,0x00,0xA8,0x3A,0x30,0x92,0xF2,0x9B, 0x3D,0x38,0x92,0x92,0xC3,0xB8,0x6B,0x29,0x01,0x01,0xB2,0x2F,0x09,0x19,0x18,0x01, 0x3B,0x7B,0x10,0xA1,0x90,0x39,0x0F,0x38,0x0A,0xB5,0xA4,0x89,0x8B,0x6A,0x2B,0x12, 0xC8,0x90,0x40,0x2A,0x9E,0x22,0x88,0x18,0x09,0x3A,0xC3,0xE8,0x09,0x59,0x08,0x12, 0x94,0xD0,0x1A,0x2C,0x38,0x00,0xA1,0x83,0xE8,0x08,0x3A,0x08,0x10,0x9E,0x83,0x1D, 0x92,0x19,0x2C,0x39,0x3B,0x59,0x04,0xE1,0x80,0x08,0x8D,0x21,0x81,0xB2,0xB2,0x02, 0x99,0x91,0xA4,0xD6,0x98,0x99,0x03,0x80,0x98,0xA7,0x91,0x09,0xA1,0xB2,0xB3,0xE1, 0x12,0x92,0xB1,0x81,0x06,0x99,0x0A,0x23,0xC4,0xB1,0xF2,0x89,0x19,0x3A,0x94,0x82, 0xE0,0x89,0x38,0x0B,0xA4,0xA5,0x80,0x80,0x8C,0x34,0xB9,0xA9,0x23,0x13,0xB9,0xC1, 0xC7,0x1B,0x89,0x10,0x20,0x11,0xE3,0xA8,0x4B,0x0B,0x40,0x91,0x90,0x1B,0x5F,0x2A, 0x18,0x82,0x91,0x0B,0x4A,0x28,0xCA,0x40,0x80,0x5B,0x2C,0x13,0xB0,0x8A,0xA9,0x5A, 0x58,0x89,0x82,0x88,0x2E,0x3B,0x31,0xA1,0x9B,0x01,0x7A,0x2C,0x01,0x91,0x93,0x3F, 0x88,0x39,0x10,0xF1,0x91,0x8B,0x48,0x0A,0x12,0xE3,0xA8,0x18,0x28,0x92,0x97,0x98, 0x99,0x19,0xA1,0x11,0xB6,0x88,0x3B,0x10,0xD3,0xC3,0xA1,0x2A,0x8A,0x49,0x04,0xF1, 0x91,0x02,0x8A,0x89,0x04,0xF1,0x98,0x80,0x18,0x12,0xE3,0x81,0x98,0x80,0x01,0xB3, 0xF2,0x99,0x12,0x2A,0xB5,0xB3,0x92,0xAA,0x19,0x50,0xB2,0xC3,0x92,0xD0,0x2B,0x68, 0x93,0x99,0xC0,0x2C,0x3E,0x80,0x20,0x08,0x93,0x0D,0x2A,0x31,0x8D,0x02,0x2B,0x91, 0x08,0x0A,0x03,0x2C,0x3C,0x52,0xB9,0xA0,0x12,0xBF,0x3A,0x29,0x01,0x88,0xC0,0x6A, 0x3C,0x0A,0x49,0x18,0x0B,0x39,0x2B,0x69,0x0A,0x84,0x2A,0x2A,0x1C,0x2A,0xC3,0x8C, 0x19,0x50,0x09,0x91,0xA7,0x8D,0x18,0x1A,0x28,0x00,0xA0,0x94,0x10,0x1F,0x20,0x90, 0x8A,0x12,0xD0,0x1A,0x5A,0x81,0x04,0xBC,0x23,0x10,0xE0,0x90,0x90,0x18,0x1A,0xA6, 0x12,0xB1,0xD0,0x4A,0x08,0x82,0x92,0xB6,0x9A,0x0A,0x12,0x88,0xC3,0xC5,0x8A,0x89, 0x20,0xB5,0x93,0x0B,0x18,0x00,0x09,0xF2,0x88,0x2A,0x4A,0x08,0x05,0xB2,0xA9,0x3B, 0x5D,0x28,0xA4,0xB1,0x00,0x19,0x19,0x7A,0xA3,0xB3,0x0A,0x90,0xA1,0xC4,0x80,0xBA, 0x50,0x13,0xC1,0xC2,0x9A,0x2A,0x7B,0x28,0x84,0xC1,0x09,0x3B,0x4E,0x20,0x91,0xA1, 0x18,0xAB,0x79,0x10,0xB4,0x08,0x9A,0x11,0x2B,0xF0,0x93,0xAA,0x01,0x6A,0x01,0x93, 0x80,0xB8,0x2A,0x5B,0x10,0x80,0x89,0x4A,0x5B,0x92,0x15,0xB2,0xA0,0x2F,0x19,0x93, 0xB8,0x95,0x80,0x1C,0x21,0xA9,0x02,0x0B,0xA0,0x5A,0x18,0x98,0x39,0x1B,0x68,0x00, 0x91,0x91,0x9C,0x39,0x3E,0x18,0x84,0xB3,0x9B,0x7A,0x08,0x18,0x0A,0xB5,0x91,0x0B, 0x28,0x39,0x19,0x90,0x0A,0x50,0xAC,0x11,0x01,0xAB,0x88,0x52,0x1B,0x83,0xC4,0xA2, 0x9A,0xAB,0x03,0x90,0x19,0x93,0x81,0x08,0x92,0x9A,0x68,0x98,0x19,0x39,0xC1,0x92, 0x8A,0x38,0x4E,0x02,0xB1,0x90,0xC3,0x18,0x2B,0x04,0xC3,0xD2,0x91,0x90,0x81,0x89, 0x13,0xF1,0x88,0x93,0xA2,0x00,0x91,0xC0,0x5B,0x21,0x99,0x93,0x06,0x9A,0x1B,0x48, 0x99,0xB7,0x90,0x89,0x18,0x1B,0x11,0xA4,0xB2,0x81,0x9A,0x08,0x97,0x98,0x91,0x10, 0xB8,0x06,0xA2,0xA0,0x29,0x2B,0x21,0xC2,0xD1,0x10,0x1A,0x4A,0x29,0xF1,0x98,0x29, 0x1B,0x31,0x10,0xA0,0xA1,0x1D,0x5A,0x29,0xB2,0x82,0xA8,0x0F,0x28,0x21,0x09,0x91, 0x82,0x4D,0x10,0xA3,0xB0,0x89,0x4C,0x39,0xA0,0xA4,0xA1,0x89,0x1E,0x28,0x29,0xA3, 0xC3,0x2D,0x19,0x01,0x49,0x01,0x9B,0x0C,0x21,0xC2,0xA2,0x93,0x7C,0x2A,0x10,0x90, /* Source: 08HH.ROM */ /* Length: 384 / 0x00000180 */ 0x75,0xF2,0xAB,0x7D,0x7E,0x5C,0x3B,0x4B,0x3C,0x4D,0x4A,0x02,0xB3,0xC5,0xE7,0xE3, 0x92,0xB3,0xC4,0xB3,0xC3,0x8A,0x3B,0x5D,0x5C,0x3A,0x84,0xC2,0x91,0xA4,0xE7,0xF7, 0xF7,0xF4,0xA1,0x1B,0x49,0xA5,0xB1,0x1E,0x7F,0x5A,0x00,0x89,0x39,0xB7,0xA8,0x3D, 0x4A,0x84,0xE7,0xF7,0xE2,0x2D,0x4C,0x3A,0x4E,0x7D,0x04,0xB0,0x2D,0x4B,0x10,0x80, 0xA3,0x99,0x10,0x0E,0x59,0x93,0xC4,0xB1,0x81,0xC4,0xA2,0xB2,0x88,0x08,0x3F,0x3B, 0x28,0xA6,0xC3,0xA2,0xA2,0xC5,0xC1,0x3F,0x7E,0x39,0x81,0x93,0xC2,0xA3,0xE5,0xD2, 0x80,0x93,0xB8,0x6D,0x49,0x82,0xD4,0xA1,0x90,0x01,0xA0,0x09,0x04,0xE3,0xB2,0x91, 0xB7,0xB3,0xA8,0x2A,0x03,0xF3,0xA1,0x92,0xC5,0xC3,0xB2,0x0B,0x30,0xB3,0x8E,0x6D, 0x4A,0x01,0xB4,0xB4,0xC4,0xC3,0x99,0x3B,0x12,0xE3,0xA1,0x88,0x82,0xB4,0x9A,0x5C, 0x3A,0x18,0x93,0xC3,0xB3,0xB4,0xA8,0x19,0x04,0xF3,0xA8,0x3B,0x10,0xA2,0x88,0xA5, 0xB2,0x0B,0x6D,0x4B,0x10,0x91,0x89,0x3C,0x18,0x18,0xA6,0xC4,0xC3,0x98,0x19,0x2B, 0x20,0x91,0xA0,0x4E,0x28,0x93,0xB3,0xC2,0x92,0xA9,0x5A,0x96,0xC4,0xC2,0x09,0x01, 0xC4,0xA1,0x92,0xC4,0xA1,0x89,0x10,0xA3,0xA1,0x90,0x1C,0x5A,0x01,0xC5,0xA1,0x92, 0xD4,0xB3,0xC4,0xC4,0xC3,0xA1,0x88,0x1A,0x28,0x89,0x3C,0x3A,0x3D,0x29,0x00,0x93, 0xB0,0x3D,0x28,0x80,0x91,0x82,0xE3,0x99,0x2A,0x11,0xD6,0xC3,0x99,0x29,0x82,0xC4, 0xC3,0xA1,0x0A,0x3B,0x3D,0x3A,0x02,0xC3,0xA2,0x99,0x3B,0x2C,0x7C,0x28,0x81,0xA3, 0xB2,0xA3,0xB1,0x08,0x1A,0x3C,0x18,0x2E,0x4C,0x39,0xA5,0xB3,0xB4,0xC2,0x88,0x08, 0x19,0x0A,0x49,0xB7,0xB3,0xA2,0xA1,0x92,0xA1,0x93,0xB1,0x0C,0x7D,0x39,0x93,0xB3, 0xB1,0x1A,0x19,0x5D,0x28,0xA6,0xC4,0xB2,0x90,0x09,0x2A,0x18,0x1B,0x5B,0x28,0x88, 0x2C,0x29,0x82,0xA0,0x18,0x91,0x2D,0x29,0x2B,0x5C,0x4C,0x3B,0x4C,0x28,0x80,0x92, 0x90,0x09,0x2B,0x28,0x1D,0x6B,0x11,0xC5,0xB2,0x0B,0x39,0x09,0x4D,0x28,0x88,0x00, 0x1B,0x28,0x94,0xE3,0xA0,0x1A,0x28,0xB5,0xB4,0xB3,0xB2,0x93,0xE2,0x91,0x92,0xD4, 0xA0,0x1B,0x4A,0x01,0xA1,0x88,0x2D,0x5C,0x3B,0x28,0x08,0x93,0xD4,0xB2,0x91,0xB4, 0xA0,0x3E,0x3B,0x4B,0x3B,0x29,0x08,0x93,0x9B,0x7B,0x3A,0x19,0x00,0x80,0x80,0xA0, /* Source: 10TOM.ROM */ /* Length: 640 / 0x00000280 */ 0x77,0x27,0x87,0x01,0x2D,0x4F,0xC3,0xC1,0x92,0x91,0x89,0x59,0x83,0x1A,0x32,0xC2, 0x95,0xB1,0x81,0x88,0x81,0x4A,0x3D,0x11,0x9E,0x0B,0x88,0x0C,0x18,0x3B,0x11,0x11, 0x91,0x00,0xA0,0xE2,0x0A,0x48,0x13,0x24,0x81,0x48,0x1B,0x39,0x1C,0x83,0x84,0xA1, 0xD1,0x8E,0x8A,0x0B,0xC0,0x98,0x92,0xB8,0x39,0x90,0x10,0x92,0xF0,0xB5,0x88,0x32, 0x49,0x51,0x21,0x03,0x82,0x10,0x8A,0x7A,0x09,0x00,0xA2,0xCA,0x1B,0xCC,0x1C,0xB9, 0x8E,0x89,0x89,0xA1,0x89,0x92,0x29,0x11,0x60,0x40,0x14,0x22,0x32,0x78,0x40,0x01, 0x02,0x90,0x81,0xAB,0x0B,0x00,0xAF,0x99,0xCC,0xAB,0xDA,0xA9,0x99,0x1B,0x30,0x14, 0x92,0x22,0x19,0x68,0x32,0x14,0x26,0x13,0x23,0x23,0x20,0x12,0x9A,0xA8,0xB9,0xFA, 0xAA,0xCA,0xCC,0x0C,0xA8,0xAE,0x88,0xB9,0x88,0xA0,0x02,0x21,0x50,0x43,0x03,0x81, 0x2A,0x11,0x34,0x63,0x24,0x33,0x22,0x38,0x8B,0xEA,0xAE,0x99,0xA0,0x90,0x82,0x00, 0x89,0xBF,0x8A,0xE8,0xA9,0x90,0x01,0x12,0x13,0x12,0x08,0xA9,0xAA,0xC9,0x22,0x63, 0x63,0x12,0x44,0x00,0x10,0x88,0x9C,0x98,0xA1,0x85,0x03,0x32,0x36,0x80,0x89,0xDB, 0xDB,0xBB,0xB9,0xBA,0x01,0x81,0x28,0x19,0xCB,0xFA,0xBC,0x09,0x13,0x37,0x34,0x34, 0x23,0x31,0x20,0x10,0x00,0x00,0x28,0x38,0x10,0x88,0xEC,0x8D,0xCB,0xBC,0xCC,0xBB, 0xBB,0xC9,0x99,0x00,0x00,0x33,0x11,0x22,0x81,0x07,0x41,0x54,0x34,0x34,0x22,0x31, 0x00,0x88,0x9A,0x9B,0x98,0xAB,0x8E,0x9B,0xBD,0x9C,0xBC,0xBB,0xDA,0xAA,0xA9,0x99, 0x18,0x38,0x60,0x20,0x31,0x13,0x13,0x51,0x14,0x31,0x53,0x33,0x35,0x22,0x01,0x8A, 0x9C,0xA9,0xCA,0xC9,0xA8,0x00,0x10,0x81,0x9C,0x9E,0xAB,0xCC,0xAB,0xBA,0x98,0x30, 0x52,0x03,0x81,0x08,0x9C,0xAC,0xAC,0x18,0x11,0x03,0x51,0x61,0x41,0x31,0x31,0x02, 0x01,0x20,0x24,0x43,0x44,0x40,0x30,0x10,0xBC,0xBE,0xCB,0xDB,0xAB,0xBA,0x99,0x98, 0x99,0xAA,0xBD,0xAA,0xC8,0x90,0x11,0x53,0x37,0x23,0x43,0x34,0x33,0x33,0x33,0x11, 0x28,0x00,0x19,0xA9,0x9A,0xCB,0xCE,0xBB,0xEB,0xBC,0xBB,0xCA,0xBA,0xA8,0x88,0x11, 0x12,0x21,0x20,0x22,0x26,0x26,0x23,0x23,0x43,0x24,0x22,0x32,0x20,0x31,0x81,0x9A, 0xBC,0xBC,0xCB,0xBD,0x9A,0xA9,0x90,0x98,0xBA,0xCC,0xCB,0xBC,0x8B,0x88,0x22,0x35, 0x23,0x12,0x99,0x8B,0xAA,0xAA,0x89,0x82,0x93,0x31,0x42,0x23,0x23,0x21,0x32,0x11, 0x20,0x13,0x13,0x24,0x24,0x24,0x22,0x11,0x8A,0x9E,0xAC,0xAC,0xAA,0xBA,0xAA,0xAB, 0xBD,0xBC,0xCB,0xCB,0xA9,0xA8,0x91,0x12,0x44,0x43,0x44,0x34,0x34,0x42,0x33,0x42, 0x21,0x11,0x11,0x88,0x80,0xAA,0x0B,0xAC,0xCB,0xEC,0xAC,0xBA,0xCA,0xAB,0x9A,0x99, 0x80,0x91,0x09,0x08,0x10,0x22,0x44,0x43,0x44,0x33,0x43,0x22,0x13,0x21,0x22,0x20, 0x09,0x88,0xB9,0xC8,0xBB,0xAB,0xAB,0xA9,0xA9,0x9B,0x9B,0x99,0x90,0x90,0x00,0x81, 0x00,0x08,0x09,0x8A,0x9A,0xAA,0xA9,0xA9,0x99,0x90,0x80,0x01,0x80,0x00,0x09,0x31, 0x32,0x44,0x33,0x43,0x34,0x33,0x24,0x22,0x23,0x12,0x10,0x09,0x9B,0xAB,0xCA,0xCC, 0xBB,0xCB,0xDA,0xCA,0xAB,0xCA,0xAB,0xA9,0xA8,0x92,0x12,0x43,0x53,0x35,0x23,0x33, 0x43,0x43,0x52,0x22,0x22,0x21,0x01,0x09,0x89,0xA9,0xBB,0xBD,0xBC,0xCB,0xDA,0xAB, 0xAB,0xAB,0xAA,0xA9,0x99,0xA8,0x09,0x01,0x11,0x34,0x25,0x23,0x33,0x51,0x22,0x31, 0x12,0x20,0x21,0x12,0x10,0x80,0x99,0x9A,0x99,0x99,0x88,0x08,0x00,0x88,0xA9,0x99, 0x99,0x80,0x80,0x10,0x01,0x00,0x9A,0xAA,0xBB,0xBA,0xBA,0xA9,0x99,0x99,0x89,0x99, 0x99,0x00,0x01,0x33,0x35,0x24,0x23,0x34,0x23,0x33,0x34,0x33,0x43,0x32,0x21,0x88, 0xAB,0xBD,0xBB,0xDB,0xAB,0xBA,0xBB,0xDA,0xBB,0xCB,0xBB,0xBC,0xA8,0x90,0x01,0x12, 0x23,0x43,0x53,0x34,0x34,0x39,0x80,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00, /* Source: 20RIM.ROM */ /* Length: 128 / 0x00000080 */ 0x0F,0xFF,0x73,0x8E,0x71,0xCD,0x00,0x49,0x10,0x90,0x21,0x49,0xA0,0xDB,0x02,0x3A, 0xE3,0x0A,0x50,0x98,0xC0,0x59,0xA2,0x99,0x09,0x22,0xA2,0x80,0x10,0xA8,0x5B,0xD2, 0x88,0x21,0x09,0x96,0xA8,0x10,0x0A,0xE0,0x08,0x48,0x19,0xAB,0x52,0xA8,0x92,0x0C, 0x03,0x19,0xE2,0x0A,0x12,0xC2,0x81,0x1E,0x01,0xD0,0x48,0x88,0x98,0x01,0x49,0x91, 0xAA,0x2C,0x25,0x89,0x88,0xB5,0x81,0xA2,0x9A,0x12,0x9E,0x38,0x3B,0x81,0x9B,0x59, 0x01,0x93,0xCA,0x4A,0x21,0xA0,0x3D,0x0A,0x39,0x3D,0x12,0xA8,0x3F,0x18,0x01,0x92, 0x1C,0x00,0xB2,0x48,0xB9,0x94,0xA3,0x19,0x4F,0x19,0xB2,0x32,0x90,0xBA,0x01,0xE6, 0x91,0x80,0xC1,0xA4,0x2A,0x08,0xA1,0xB1,0x25,0xD2,0x88,0x99,0x21,0x80,0x88,0x80, }; BambooTracker-0.6.5/BambooTracker/chip/mame/mame_2608.cpp000066400000000000000000000075461476276175200227510ustar00rootroot00000000000000/* * Copyright (C) 2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "mame_2608.hpp" #include extern "C" { #include "fmopn.h" } namespace chip { namespace // SSG callbacks { void setSsgClock(void* param, uint32_t clock) { auto state = reinterpret_cast(param); if (state->ssg) PSG_set_clock(state->ssg, clock); } void writeSsg(void* param, uint8_t address, uint8_t data) { auto state = reinterpret_cast(param); if (state->ssg) PSG_writeIO(state->ssg, address, data); } uint8_t readSsg(void* param) { auto state = reinterpret_cast(param); return (state->ssg ? PSG_readIO(state->ssg) : 0); } void resetSsg(void* param) { auto state = reinterpret_cast(param); if (state->ssg) PSG_reset(state->ssg); } const ssg_callbacks SSG_INTF = { setSsgClock, writeSsg, readSsg, resetSsg }; } Mame2608::~Mame2608() { stopDevice(); } int Mame2608::startDevice(int clock, int& rateSsg, uint32_t dramSize) { int clockSsg = clock / 4; rateSsg = clockSsg / 8; state_.ssg = PSG_new(clockSsg, rateSsg); if (!state_.ssg) return 0; PSG_setVolumeMode(state_.ssg, 1); // YM2149 volume mode int rate = clock / 144; // FM synthesis rate is clock / 2 / 72 state_.chip = ym2608_init(&state_, clock, rate, nullptr, nullptr); if (!state_.chip) return 0; ym2608_link_ssg(state_.chip, &SSG_INTF, &state_); ym2608_alloc_pcmromb(state_.chip, dramSize); return rate; } void Mame2608::stopDevice() { if (state_.chip) { ym2608_shutdown(state_.chip); state_.chip = nullptr; } if (state_.ssg) { PSG_delete(state_.ssg); state_.ssg = nullptr; } } void Mame2608::resetDevice() { ym2608_reset_chip(state_.chip); // Also reset SSG } void Mame2608::writeAddressToPortA(uint8_t address) { ym2608_write(state_.chip, 0, address); } void Mame2608::writeAddressToPortB(uint8_t address) { ym2608_write(state_.chip, 2, address); } void Mame2608::writeDataToPortA(uint8_t data) { ym2608_write(state_.chip, 1, data); } void Mame2608::writeDataToPortB(uint8_t data) { ym2608_write(state_.chip, 3, data); } uint8_t Mame2608::readData() { return ym2608_read(state_.chip, 1); } void Mame2608::updateStream(sample** outputs, int nSamples) { ym2608_update_one(state_.chip, nSamples, outputs); } void Mame2608::updateSsgStream(sample** outputs, int nSamples) { if (state_.ssg) { sample* bufl = outputs[STEREO_LEFT]; sample* bufr = outputs[STEREO_RIGHT]; for (int i = 0; i < nSamples; ++i) { int16_t s = PSG_calc(state_.ssg) << 1; *bufl++ = s; *bufr++ = s; } } else { std::fill_n(outputs[STEREO_LEFT], nSamples, 0); std::fill_n(outputs[STEREO_RIGHT], nSamples, 0); } } } namespace { sample* DUMMY_BUF[] = { nullptr, nullptr }; } void ym2608_update_request(void* param) { auto state = reinterpret_cast(param); ym2608_update_one(state->chip, 0, DUMMY_BUF); } BambooTracker-0.6.5/BambooTracker/chip/mame/mame_2608.hpp000066400000000000000000000036211476276175200227440ustar00rootroot00000000000000/* * Copyright (C) 2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "../2608_interface.hpp" #include "emu2149.h" namespace chip { struct Mame2608State { void* chip = nullptr; PSG* ssg = nullptr; }; class Mame2608 final : public Ym2608Interface { public: ~Mame2608() override; int startDevice(int clock, int& rateSsg, uint32_t dramSize) override; void stopDevice() override; void resetDevice() override; void writeAddressToPortA(uint8_t address) override; void writeAddressToPortB(uint8_t address) override; void writeDataToPortA(uint8_t data) override; void writeDataToPortB(uint8_t data) override; uint8_t readData() override; void updateStream(sample** outputs, int nSamples) override; void updateSsgStream(sample** outputs, int nSamples) override; private: Mame2608State state_; }; } extern "C" { void ym2608_update_request(void* param); } BambooTracker-0.6.5/BambooTracker/chip/mame/mamedefs.h000066400000000000000000000035051476276175200225700ustar00rootroot00000000000000#ifndef __MAMEDEFS_H__ #define __MAMEDEFS_H__ #include // for NULL #include #include "../chip_defs.h" /* * Copied from mame/src/osd/osdcomm.h * license:BSD-3-Clause * copyright-holders:Aaron Giles */ /* 8-bit values */ typedef unsigned char UINT8; typedef signed char INT8; /* 16-bit values */ typedef unsigned short UINT16; typedef signed short INT16; /* 32-bit values */ #ifndef _WINDOWS_H typedef unsigned int UINT32; typedef signed int INT32; #endif /* 64-bit values */ #ifndef _WINDOWS_H #ifdef _MSC_VER typedef signed __int64 INT64; typedef unsigned __int64 UINT64; #else __extension__ typedef unsigned long long UINT64; __extension__ typedef signed long long INT64; #endif #endif /* * Copied from mame/src/emu/memory.h * license:BSD-3-Clause * copyright-holders:Aaron Giles */ /* offsets and addresses are 32-bit (for now...) */ typedef UINT32 offs_t; /* * Copied from mame/src/emu/emucore.h * license:BSD-3-Clause * copyright-holders:Aaron Giles */ /* stream_sample_t is used to represent a single sample in a sound stream */ /*typedef INT32 stream_sample_t;*/ typedef sample stream_sample_t; // M_PI is not part of the C/C++ standards and is not present on // strict ANSI compilers or when compiling under GCC with -ansi #ifndef M_PI #define M_PI 3.14159265358979323846 #endif /*************************************************/ typedef void (*DEVCB_SRATE_CHG)(void* info, UINT32 newSRate); #ifndef INLINE #if defined(_MSC_VER) #define INLINE static __inline #elif defined(__GNUC__) #define INLINE static __inline__ #else #define INLINE static inline #endif #endif #endif /* __MAMEDEFS_H__ */ BambooTracker-0.6.5/BambooTracker/chip/mame/ymdeltat.c000066400000000000000000000553651476276175200226400ustar00rootroot00000000000000/* ** ** File: ymdeltat.c ** ** YAMAHA DELTA-T adpcm sound emulation subroutine ** used by fmopl.c (Y8950) and fm.c (YM2608 and YM2610/B) ** ** Base program is YM2610 emulator by Hiromitsu Shioya. ** Written by Tatsuyuki Satoh ** Improvements by Jarek Burczynski (bujar at mame dot net) ** ** ** History: ** ** 03-08-2003 Jarek Burczynski: ** - fixed BRDY flag implementation. ** ** 24-07-2003 Jarek Burczynski, Frits Hilderink: ** - fixed delault value for control2 in YM_DELTAT_ADPCM_Reset ** ** 22-07-2003 Jarek Burczynski, Frits Hilderink: ** - fixed external memory support ** ** 15-06-2003 Jarek Burczynski: ** - implemented CPU -> AUDIO ADPCM synthesis (via writes to the ADPCM data reg $08) ** - implemented support for the Limit address register ** - supported two bits from the control register 2 ($01): RAM TYPE (x1 bit/x8 bit), ROM/RAM ** - implemented external memory access (read/write) via the ADPCM data reg reads/writes ** Thanks go to Frits Hilderink for the example code. ** ** 14-06-2003 Jarek Burczynski: ** - various fixes to enable proper support for status register flags: BSRDY, PCM BSY, ZERO ** - modified EOS handling ** ** 05-04-2003 Jarek Burczynski: ** - implemented partial support for external/processor memory on sample replay ** ** 01-12-2002 Jarek Burczynski: ** - fixed first missing sound in gigandes thanks to previous fix (interpolator) by ElSemi ** - renamed/removed some YM_DELTAT struct fields ** ** 28-12-2001 Acho A. Tang ** - added EOS status report on ADPCM playback. ** ** 05-08-2001 Jarek Burczynski: ** - now_step is initialized with 0 at the start of play. ** ** 12-06-2001 Jarek Burczynski: ** - corrected end of sample bug in YM_DELTAT_ADPCM_CALC. ** Checked on real YM2610 chip - address register is 24 bits wide. ** Thanks go to Stefan Jokisch (stefan.jokisch@gmx.de) for tracking down the problem. ** ** TO DO: ** Check size of the address register on the other chips.... ** ** Version 0.72 ** ** sound chips that have this unit: ** YM2608 OPNA ** YM2610/B OPNB ** Y8950 MSX AUDIO ** */ #include "ymdeltat.h" #define YM_DELTAT_DELTA_MAX (24576) #define YM_DELTAT_DELTA_MIN (127) #define YM_DELTAT_DELTA_DEF (127) #define YM_DELTAT_DECODE_RANGE 32768 #define YM_DELTAT_DECODE_MIN (-(YM_DELTAT_DECODE_RANGE)) #define YM_DELTAT_DECODE_MAX ((YM_DELTAT_DECODE_RANGE)-1) /* Forecast to next Forecast (rate = *8) */ /* 1/8 , 3/8 , 5/8 , 7/8 , 9/8 , 11/8 , 13/8 , 15/8 */ static const INT32 ym_deltat_decode_tableB1[16] = { 1, 3, 5, 7, 9, 11, 13, 15, -1, -3, -5, -7, -9, -11, -13, -15, }; /* delta to next delta (rate= *64) */ /* 0.9 , 0.9 , 0.9 , 0.9 , 1.2 , 1.6 , 2.0 , 2.4 */ static const INT32 ym_deltat_decode_tableB2[16] = { 57, 57, 57, 57, 77, 102, 128, 153, 57, 57, 57, 57, 77, 102, 128, 153 }; #if 0 void YM_DELTAT_BRDY_callback(YM_DELTAT *DELTAT) { logerror("BRDY_callback reached (flag set) !\n"); /* set BRDY bit in status register */ if(DELTAT->status_set_handler != NULL && DELTAT->status_change_BRDY_bit) (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); } #endif UINT8 YM_DELTAT_ADPCM_Read(YM_DELTAT *DELTAT) { UINT8 v = 0; /* external memory read */ if ( (DELTAT->portstate & 0xe0)==0x20 ) { /* two dummy reads */ if (DELTAT->memread) { DELTAT->now_addr = DELTAT->start << 1; DELTAT->memread--; return 0; } if ( DELTAT->now_addr != (DELTAT->end<<1) ) { v = DELTAT->memory[(DELTAT->now_addr>>1)&DELTAT->memory_mask]; /*logerror("YM Delta-T memory read $%08x, v=$%02x\n", DELTAT->now_addr >> 1, v);*/ DELTAT->now_addr+=2; /* two nibbles at a time */ /* reset BRDY bit in status register, which means we are reading the memory now */ if (DELTAT->status_reset_handler && DELTAT->status_change_BRDY_bit) (DELTAT->status_reset_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); /* setup a timer that will callback us in 10 master clock cycles for Y8950 * in the callback set the BRDY flag to 1 , which means we have another data ready. * For now, we don't really do this; we simply reset and set the flag in zero time, so that the IRQ will work. */ /* set BRDY bit in status register */ if (DELTAT->status_set_handler != NULL && DELTAT->status_change_BRDY_bit) (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); } else { /* set EOS bit in status register */ if (DELTAT->status_set_handler != NULL && DELTAT->status_change_EOS_bit) (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_EOS_bit); } } return v; } /* 0-DRAM x1, 1-ROM, 2-DRAM x8, 3-ROM (3 is bad setting - not allowed by the manual) */ static const UINT8 dram_rightshift[4]={3,0,0,0}; /* DELTA-T ADPCM write register */ void YM_DELTAT_ADPCM_Write(YM_DELTAT *DELTAT,int r,int v) { if(r>=0x10) return; DELTAT->reg[r] = (UINT8)v; /* stock data */ switch( r ) { case 0x00: /* START: Accessing *external* memory is started when START bit (D7) is set to "1", so you must set all conditions needed for recording/playback before starting. If you access *CPU-managed* memory, recording/playback starts after read/write of ADPCM data register $08. REC: 0 = ADPCM synthesis (playback) 1 = ADPCM analysis (record) MEMDATA: 0 = processor (*CPU-managed*) memory (means: using register $08) 1 = external memory (using start/end/limit registers to access memory: RAM or ROM) SPOFF: controls output pin that should disable the speaker while ADPCM analysis RESET and REPEAT only work with external memory. some examples: value: START, REC, MEMDAT, REPEAT, SPOFF, x,x,RESET meaning: C8 1 1 0 0 1 0 0 0 Analysis (recording) from AUDIO to CPU (to reg $08), sample rate in PRESCALER register E8 1 1 1 0 1 0 0 0 Analysis (recording) from AUDIO to EXT.MEMORY, sample rate in PRESCALER register 80 1 0 0 0 0 0 0 0 Synthesis (playing) from CPU (from reg $08) to AUDIO,sample rate in DELTA-N register a0 1 0 1 0 0 0 0 0 Synthesis (playing) from EXT.MEMORY to AUDIO, sample rate in DELTA-N register 60 0 1 1 0 0 0 0 0 External memory write via ADPCM data register $08 20 0 0 1 0 0 0 0 0 External memory read via ADPCM data register $08 */ /* handle emulation mode */ if(DELTAT->emulation_mode == YM_DELTAT_EMULATION_MODE_YM2610) { v |= 0x20; /* YM2610 always uses external memory and doesn't even have memory flag bit. */ } DELTAT->portstate = v & (0x80|0x40|0x20|0x10|0x01); /* start, rec, memory mode, repeat flag copy, reset(bit0) */ if( DELTAT->portstate&0x80 )/* START,REC,MEMDATA,REPEAT,SPOFF,--,--,RESET */ { /* set PCM BUSY bit */ DELTAT->PCM_BSY = 1; /* start ADPCM */ DELTAT->now_step = 0; DELTAT->acc = 0; DELTAT->prev_acc = 0; DELTAT->adpcml = 0; DELTAT->adpcmd = YM_DELTAT_DELTA_DEF; DELTAT->now_data = 0; /*if (DELTAT->start > DELTAT->end) //logerror("DeltaT-Warning: Start: %06X, End: %06X\n", DELTAT->start, DELTAT->end); logerror("DeltaT-Warning: Start: %06X, End: %06X, Limit %06X, MemMask %06X\n", DELTAT->start, DELTAT->end, DELTAT->limit, DELTAT->memory_mask);*/ } if( DELTAT->portstate&0x20 ) /* do we access external memory? */ { DELTAT->now_addr = DELTAT->start << 1; DELTAT->memread = 2; /* two dummy reads needed before accesing external memory via register $08*/ /* if yes, then let's check if ADPCM memory is mapped and big enough */ if(DELTAT->memory == NULL) { //logerror("YM Delta-T ADPCM rom not mapped\n"); DELTAT->portstate = 0x00; DELTAT->PCM_BSY = 0; } else { if( (DELTAT->end & DELTAT->memory_mask) >= DELTAT->memory_size ) /* Check End in Range */ { //logerror("YM Delta-T ADPCM end out of range: $%08x\n", DELTAT->end); //logerror("YM Delta-T ADPCM end out of range: %06X >= %06X\n", DELTAT->end, DELTAT->memory_size); DELTAT->end = (DELTAT->end & ~DELTAT->memory_mask) | (DELTAT->memory_size - 1); } if( (DELTAT->start & DELTAT->memory_mask) >= DELTAT->memory_size ) /* Check Start in Range */ { //logerror("YM Delta-T ADPCM start out of range: $%08x\n", DELTAT->start); DELTAT->portstate = 0x00; DELTAT->PCM_BSY = 0; } } } else /* we access CPU memory (ADPCM data register $08) so we only reset now_addr here */ { DELTAT->now_addr = 0; } if( DELTAT->portstate&0x01 ) { DELTAT->portstate = 0x00; /* clear PCM BUSY bit (in status register) */ DELTAT->PCM_BSY = 0; /* set BRDY flag */ if (DELTAT->status_set_handler != NULL && DELTAT->status_change_BRDY_bit) (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); } break; case 0x01: /* L,R,-,-,SAMPLE,DA/AD,RAMTYPE,ROM */ /* handle emulation mode */ if(DELTAT->emulation_mode == YM_DELTAT_EMULATION_MODE_YM2610) { /* YM2610 always uses ROM as an external memory and doesn't have ROM/RAM memory flag bit. */ v = (v & ~3) | (DELTAT->control2 & 3); } DELTAT->pan = &DELTAT->output_pointer[(v>>6)&0x03]; if ((DELTAT->control2 & 3) != (v & 3)) { /*0-DRAM x1, 1-ROM, 2-DRAM x8, 3-ROM (3 is bad setting - not allowed by the manual) */ UINT8 new_shift = DELTAT->portshift_base - dram_rightshift[v&3]; if (DELTAT->now_portshift != new_shift) { DELTAT->now_portshift = new_shift; /* final shift value depends on chip type and memory type selected: 8 for YM2610 (ROM only), 5 for ROM for Y8950 and YM2608, 5 for x8bit DRAMs for Y8950 and YM2608, 2 for x1bit DRAMs for Y8950 and YM2608. */ /* refresh addresses */ DELTAT->address_mask = (0x10000 << (DELTAT->now_portshift + 1)) - 1; DELTAT->start = (DELTAT->reg[0x3]*0x0100 | DELTAT->reg[0x2]) << DELTAT->now_portshift; DELTAT->end = (DELTAT->reg[0x5]*0x0100 | DELTAT->reg[0x4]) << DELTAT->now_portshift; DELTAT->end |= (1 << DELTAT->now_portshift ) - 1; DELTAT->limit = (DELTAT->reg[0xd]*0x0100 | DELTAT->reg[0xc]) << DELTAT->now_portshift; } } DELTAT->control2 = (UINT8)v; break; case 0x02: /* Start Address L */ case 0x03: /* Start Address H */ DELTAT->start = (DELTAT->reg[0x3]*0x0100 | DELTAT->reg[0x2]) << DELTAT->now_portshift; /*logerror("DELTAT start: 02=%2x 03=%2x addr=%8x\n",DELTAT->reg[0x2], DELTAT->reg[0x3],DELTAT->start );*/ break; case 0x04: /* Stop Address L */ case 0x05: /* Stop Address H */ DELTAT->end = (DELTAT->reg[0x5]*0x0100 | DELTAT->reg[0x4]) << DELTAT->now_portshift; DELTAT->end |= (1 << DELTAT->now_portshift ) - 1; /*logerror("DELTAT end : 04=%2x 05=%2x addr=%8x\n",DELTAT->reg[0x4], DELTAT->reg[0x5],DELTAT->end );*/ break; case 0x06: /* Prescale L (ADPCM and Record frq) */ case 0x07: /* Prescale H */ break; case 0x08: /* ADPCM data */ /* some examples: value: START, REC, MEMDAT, REPEAT, SPOFF, x,x,RESET meaning: C8 1 1 0 0 1 0 0 0 Analysis (recording) from AUDIO to CPU (to reg $08), sample rate in PRESCALER register E8 1 1 1 0 1 0 0 0 Analysis (recording) from AUDIO to EXT.MEMORY, sample rate in PRESCALER register 80 1 0 0 0 0 0 0 0 Synthesis (playing) from CPU (from reg $08) to AUDIO,sample rate in DELTA-N register a0 1 0 1 0 0 0 0 0 Synthesis (playing) from EXT.MEMORY to AUDIO, sample rate in DELTA-N register 60 0 1 1 0 0 0 0 0 External memory write via ADPCM data register $08 20 0 0 1 0 0 0 0 0 External memory read via ADPCM data register $08 */ /* external memory write */ if ( (DELTAT->portstate & 0xe0)==0x60 ) { if (DELTAT->memread) { DELTAT->now_addr = DELTAT->start << 1; DELTAT->memread = 0; } /*logerror("YM Delta-T memory write $%08x, v=$%02x\n", DELTAT->now_addr >> 1, v);*/ if ( DELTAT->now_addr != (DELTAT->end<<1) ) { DELTAT->memory[(DELTAT->now_addr>>1)&DELTAT->memory_mask] = (UINT8)v; DELTAT->now_addr+=2; /* two nibbles at a time */ /* reset BRDY bit in status register, which means we are processing the write */ if (DELTAT->status_reset_handler && DELTAT->status_change_BRDY_bit) (DELTAT->status_reset_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); /* setup a timer that will callback us in 10 master clock cycles for Y8950 * in the callback set the BRDY flag to 1 , which means we have written the data. * For now, we don't really do this; we simply reset and set the flag in zero time, so that the IRQ will work. */ /* set BRDY bit in status register */ if (DELTAT->status_set_handler != NULL && DELTAT->status_change_BRDY_bit) (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); } else { /* set EOS bit in status register */ if (DELTAT->status_set_handler != NULL && DELTAT->status_change_EOS_bit) (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_EOS_bit); } return; } /* ADPCM synthesis from CPU */ if ( (DELTAT->portstate & 0xe0)==0x80 ) { DELTAT->CPU_data = (UINT8)v; /* Reset BRDY bit in status register, which means we are full of data */ if (DELTAT->status_reset_handler && DELTAT->status_change_BRDY_bit) (DELTAT->status_reset_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); return; } break; case 0x09: /* DELTA-N L (ADPCM Playback Prescaler) */ case 0x0a: /* DELTA-N H */ DELTAT->delta = (DELTAT->reg[0xa]*0x0100 | DELTAT->reg[0x9]); DELTAT->step = (UINT32)( (double)(DELTAT->delta /* *(1<<(YM_DELTAT_SHIFT-16)) */ ) * (DELTAT->freqbase) ); /*logerror("DELTAT deltan:09=%2x 0a=%2x\n",DELTAT->reg[0x9], DELTAT->reg[0xa]);*/ break; case 0x0b: /* Output level control (volume, linear) */ { INT32 oldvol = DELTAT->volume; DELTAT->volume = (v&0xff) * (DELTAT->output_range/256) / YM_DELTAT_DECODE_RANGE; /* v * ((1<<16)>>8) >> 15; * thus: v * (1<<8) >> 15; * thus: output_range must be (1 << (15+8)) at least * v * ((1<<23)>>8) >> 15; * v * (1<<15) >> 15; */ /*logerror("DELTAT vol = %2x\n",v&0xff);*/ if (oldvol != 0) { DELTAT->adpcml = (int)((double)DELTAT->adpcml / (double)oldvol * (double)DELTAT->volume); } } break; case 0x0c: /* Limit Address L */ case 0x0d: /* Limit Address H */ { //UINT32 oldLimit = DELTAT->limit; DELTAT->limit = (DELTAT->reg[0xd]*0x0100 | DELTAT->reg[0xc]) << DELTAT->now_portshift; /*logerror("DELTAT limit: 0c=%2x 0d=%2x addr=%8x\n",DELTAT->reg[0xc], DELTAT->reg[0xd],DELTAT->limit );*/ /*if (oldLimit != DELTAT->limit) logerror("DELTAT limit: %02x=%02x addr=%06x\n",r, DELTAT->reg[r],DELTAT->limit );*/ } break; } } void YM_DELTAT_ADPCM_Init(YM_DELTAT *DELTAT,int emulation_mode,int portshift,INT32* output_ptr,int output_range) { DELTAT->emulation_mode = (UINT8)emulation_mode; if (DELTAT->emulation_mode == YM_DELTAT_EMULATION_MODE_YM2610) { DELTAT->portstate = 0x20; DELTAT->control2 = 0x01; } else { DELTAT->portstate = 0x00; DELTAT->control2 = 0x00; /* default setting depends on the emulation mode. MSX demo called "facdemo_4" doesn't setup control2 register at all and still works */ } DELTAT->portshift_base = (UINT8)portshift; DELTAT->output_pointer = output_ptr; DELTAT->output_range = output_range; DELTAT->now_portshift = DELTAT->portshift_base - dram_rightshift[DELTAT->control2 & 3]; DELTAT->address_mask = (0x10000 << (DELTAT->now_portshift + 1)) - 1; } void YM_DELTAT_ADPCM_Reset(YM_DELTAT *DELTAT,int pan) { DELTAT->now_addr = 0; DELTAT->now_step = 0; DELTAT->step = 0; DELTAT->start = 0; DELTAT->end = 0; DELTAT->limit = (UINT32)~0; /* this way YM2610 and Y8950 (both of which don't have limit address reg) will still work */ DELTAT->volume = 0; DELTAT->pan = &DELTAT->output_pointer[pan]; DELTAT->acc = 0; DELTAT->prev_acc = 0; DELTAT->adpcmd = 127; DELTAT->adpcml = 0; if (DELTAT->emulation_mode == YM_DELTAT_EMULATION_MODE_YM2610) { DELTAT->portstate &= 0x20; DELTAT->control2 &= 0x03; } else { DELTAT->portstate = 0x00; DELTAT->control2 = 0x00; } DELTAT->now_portshift = DELTAT->portshift_base - dram_rightshift[DELTAT->control2 & 3]; DELTAT->address_mask = (0x10000 << (DELTAT->now_portshift + 1)) - 1; /* The flag mask register disables the BRDY after the reset, however ** as soon as the mask is enabled the flag needs to be set. */ /* set BRDY bit in status register */ if (DELTAT->status_set_handler != NULL && DELTAT->status_change_BRDY_bit) (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); } #define YM_DELTAT_Limit(val,max,min) \ { \ if ( val > max ) val = max; \ else if ( val < min ) val = min; \ } INLINE void YM_DELTAT_synthesis_from_external_memory(YM_DELTAT *DELTAT) { UINT32 step; int data; DELTAT->now_step += DELTAT->step; if ( DELTAT->now_step >= (1<now_step >> YM_DELTAT_SHIFT; DELTAT->now_step &= (1<now_addr == (DELTAT->limit<<1) ) DELTAT->now_addr = 0; if ( DELTAT->now_addr == (DELTAT->end<<1) ) { /* 12-06-2001 JB: corrected comparison. Was > instead of == */ if( DELTAT->portstate&0x10 ){ /* repeat start */ DELTAT->now_addr = DELTAT->start<<1; DELTAT->acc = 0; DELTAT->adpcmd = YM_DELTAT_DELTA_DEF; DELTAT->prev_acc = 0; }else{ /* set EOS bit in status register */ if(DELTAT->status_set_handler != NULL && DELTAT->status_change_EOS_bit) (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_EOS_bit); /* clear PCM BUSY bit (reflected in status register) */ DELTAT->PCM_BSY = 0; DELTAT->portstate = 0; DELTAT->adpcml = 0; DELTAT->prev_acc = 0; return; } } if( DELTAT->now_addr&1 ) data = DELTAT->now_data & 0x0f; else { DELTAT->now_data = DELTAT->memory[(DELTAT->now_addr>>1)&DELTAT->memory_mask]; data = DELTAT->now_data >> 4; } DELTAT->now_addr++; /* 12-06-2001 JB: */ /* YM2610 address register is 24 bits wide.*/ /* The "+1" is there because we use 1 bit more for nibble calculations.*/ /* WARNING: */ /* Side effect: we should take the size of the mapped ROM into account */ //DELTAT->now_addr &= ( (1<<(24+1))-1); DELTAT->now_addr &= DELTAT->address_mask; /* store accumulator value */ DELTAT->prev_acc = DELTAT->acc; /* Forecast to next Forecast */ DELTAT->acc += (ym_deltat_decode_tableB1[data] * DELTAT->adpcmd / 8); YM_DELTAT_Limit(DELTAT->acc,YM_DELTAT_DECODE_MAX, YM_DELTAT_DECODE_MIN); /* delta to next delta */ DELTAT->adpcmd = (DELTAT->adpcmd * ym_deltat_decode_tableB2[data] ) / 64; YM_DELTAT_Limit(DELTAT->adpcmd,YM_DELTAT_DELTA_MAX, YM_DELTAT_DELTA_MIN ); /* ElSemi: Fix interpolator. */ /*DELTAT->prev_acc = prev_acc + ((DELTAT->acc - prev_acc) / 2 );*/ }while(--step); } /* ElSemi: Fix interpolator. */ DELTAT->adpcml = DELTAT->prev_acc * (int)((1<now_step); DELTAT->adpcml += (DELTAT->acc * (int)DELTAT->now_step); DELTAT->adpcml = (DELTAT->adpcml>>YM_DELTAT_SHIFT) * (int)DELTAT->volume; /* output for work of output channels (outd[OPNxxxx])*/ *(DELTAT->pan) += DELTAT->adpcml; } INLINE void YM_DELTAT_synthesis_from_CPU_memory(YM_DELTAT *DELTAT) { UINT32 step; int data; DELTAT->now_step += DELTAT->step; if ( DELTAT->now_step >= (1<now_step >> YM_DELTAT_SHIFT; DELTAT->now_step &= (1<now_addr&1 ) { data = DELTAT->now_data & 0x0f; DELTAT->now_data = DELTAT->CPU_data; /* after we used CPU_data, we set BRDY bit in status register, * which means we are ready to accept another byte of data */ if(DELTAT->status_set_handler != NULL && DELTAT->status_change_BRDY_bit) (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); } else { data = DELTAT->now_data >> 4; } DELTAT->now_addr++; /* store accumulator value */ DELTAT->prev_acc = DELTAT->acc; /* Forecast to next Forecast */ DELTAT->acc += (ym_deltat_decode_tableB1[data] * DELTAT->adpcmd / 8); YM_DELTAT_Limit(DELTAT->acc,YM_DELTAT_DECODE_MAX, YM_DELTAT_DECODE_MIN); /* delta to next delta */ DELTAT->adpcmd = (DELTAT->adpcmd * ym_deltat_decode_tableB2[data] ) / 64; YM_DELTAT_Limit(DELTAT->adpcmd,YM_DELTAT_DELTA_MAX, YM_DELTAT_DELTA_MIN ); }while(--step); } /* ElSemi: Fix interpolator. */ DELTAT->adpcml = DELTAT->prev_acc * (int)((1<now_step); DELTAT->adpcml += (DELTAT->acc * (int)DELTAT->now_step); DELTAT->adpcml = (DELTAT->adpcml>>YM_DELTAT_SHIFT) * (int)DELTAT->volume; /* output for work of output channels (outd[OPNxxxx])*/ *(DELTAT->pan) += DELTAT->adpcml; } /* ADPCM B (Delta-T control type) */ void YM_DELTAT_ADPCM_CALC(YM_DELTAT *DELTAT) { /* some examples: value: START, REC, MEMDAT, REPEAT, SPOFF, x,x,RESET meaning: 80 1 0 0 0 0 0 0 0 Synthesis (playing) from CPU (from reg $08) to AUDIO,sample rate in DELTA-N register a0 1 0 1 0 0 0 0 0 Synthesis (playing) from EXT.MEMORY to AUDIO, sample rate in DELTA-N register C8 1 1 0 0 1 0 0 0 Analysis (recording) from AUDIO to CPU (to reg $08), sample rate in PRESCALER register E8 1 1 1 0 1 0 0 0 Analysis (recording) from AUDIO to EXT.MEMORY, sample rate in PRESCALER register 60 0 1 1 0 0 0 0 0 External memory write via ADPCM data register $08 20 0 0 1 0 0 0 0 0 External memory read via ADPCM data register $08 */ if ( (DELTAT->portstate & 0xe0)==0xa0 ) { YM_DELTAT_synthesis_from_external_memory(DELTAT); return; } if ( (DELTAT->portstate & 0xe0)==0x80 ) { /* ADPCM synthesis from CPU-managed memory (from reg $08) */ YM_DELTAT_synthesis_from_CPU_memory(DELTAT); /* change output based on data in ADPCM data reg ($08) */ return; } //todo: ADPCM analysis // if ( (DELTAT->portstate & 0xe0)==0xc0 ) // if ( (DELTAT->portstate & 0xe0)==0xe0 ) return; } INLINE UINT32 pow2_mask(UINT32 v) { if (v == 0) return 0; v --; v |= (v >> 1); v |= (v >> 2); v |= (v >> 4); v |= (v >> 8); v |= (v >> 16); return v; } void YM_DELTAT_calc_mem_mask(YM_DELTAT* DELTAT) { DELTAT->memory_mask = pow2_mask(DELTAT->memory_size); return; } BambooTracker-0.6.5/BambooTracker/chip/mame/ymdeltat.h000066400000000000000000000062561476276175200226400ustar00rootroot00000000000000#ifndef __YMDELTAT_H__ #define __YMDELTAT_H__ #include "mamedefs.h" #define YM_DELTAT_SHIFT (16) #define YM_DELTAT_EMULATION_MODE_NORMAL 0 #define YM_DELTAT_EMULATION_MODE_YM2610 1 typedef void (*STATUS_CHANGE_HANDLER)(void *chip, UINT8 status_bits); /* DELTA-T (adpcm type B) struct */ typedef struct deltat_adpcm_state { /* AT: rearranged and tightened structure */ UINT8 *memory; INT32 *output_pointer;/* pointer of output pointers */ INT32 *pan; /* pan : &output_pointer[pan] */ double freqbase; #if 0 double write_time; /* Y8950: 10 cycles of main clock; YM2608: 20 cycles of main clock */ double read_time; /* Y8950: 8 cycles of main clock; YM2608: 18 cycles of main clock */ #endif UINT32 memory_size; UINT32 memory_mask; int output_range; UINT32 address_mask; UINT32 now_addr; /* current address */ UINT32 now_step; /* current step */ UINT32 step; /* step */ UINT32 start; /* start address */ UINT32 limit; /* limit address */ UINT32 end; /* end address */ UINT32 delta; /* delta scale */ INT32 volume; /* current volume */ INT32 acc; /* shift Measurement value*/ INT32 adpcmd; /* next Forecast */ INT32 adpcml; /* current value */ INT32 prev_acc; /* leveling value */ UINT8 now_data; /* current rom data */ UINT8 CPU_data; /* current data from reg 08 */ UINT8 portstate; /* port status */ UINT8 control2; /* control reg: SAMPLE, DA/AD, RAM TYPE (x8bit / x1bit), ROM/RAM */ UINT8 portshift_base; /* address bits shift-left: ** 8 for YM2610, ** 5 for Y8950 and YM2608 */ UINT8 now_portshift; /* current address bits shift-left */ UINT8 memread; /* needed for reading/writing external memory */ /* handlers and parameters for the status flags support */ STATUS_CHANGE_HANDLER status_set_handler; STATUS_CHANGE_HANDLER status_reset_handler; /* note that different chips have these flags on different ** bits of the status register */ void * status_change_which_chip; /* this chip id */ UINT8 status_change_EOS_bit; /* 1 on End Of Sample (record/playback/cycle time of AD/DA converting has passed)*/ UINT8 status_change_BRDY_bit; /* 1 after recording 2 datas (2x4bits) or after reading/writing 1 data */ UINT8 status_change_ZERO_bit; /* 1 if silence lasts for more than 290 miliseconds on ADPCM recording */ /* neither Y8950 nor YM2608 can generate IRQ when PCMBSY bit changes, so instead of above, ** the statusflag gets ORed with PCM_BSY (below) (on each read of statusflag of Y8950 and YM2608) */ UINT8 PCM_BSY; /* 1 when ADPCM is playing; Y8950/YM2608 only */ UINT8 reg[16]; /* adpcm registers */ UINT8 emulation_mode; /* which chip we're emulating */ }YM_DELTAT; //void YM_DELTAT_BRDY_callback(YM_DELTAT *DELTAT); UINT8 YM_DELTAT_ADPCM_Read(YM_DELTAT *DELTAT); void YM_DELTAT_ADPCM_Write(YM_DELTAT *DELTAT,int r,int v); void YM_DELTAT_ADPCM_Init(YM_DELTAT *DELTAT,int emulation_mode,int portshift,INT32* output_ptr,int output_range); void YM_DELTAT_ADPCM_Reset(YM_DELTAT *DELTAT,int pan); void YM_DELTAT_ADPCM_CALC(YM_DELTAT *DELTAT); void YM_DELTAT_calc_mem_mask(YM_DELTAT* DELTAT); #endif // __YMDELTAT_H__ BambooTracker-0.6.5/BambooTracker/chip/nuked/000077500000000000000000000000001476276175200210225ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/chip/nuked/nuked_2608.cpp000066400000000000000000000076651476276175200233310ustar00rootroot00000000000000/* * Copyright (C) 2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "nuked_2608.hpp" #include #include namespace chip { namespace // SSG callbacks { void setSsgClock(void *param, int clock) { auto state = reinterpret_cast(param); if (state->ssg) PSG_set_clock(state->ssg, clock); } void writeSsg(void *param, int address, int data) { auto state = reinterpret_cast(param); if (state->ssg) PSG_writeIO(state->ssg, address, data); } int readSsg(void *param) { auto state = reinterpret_cast(param); return (state->ssg ? PSG_readIO(state->ssg) : 0); } void resetSsg(void *param) { auto state = reinterpret_cast(param); if (state->ssg) PSG_reset(state->ssg); } const struct OPN2mod_psg_callbacks SSG_INTF = { setSsgClock, writeSsg, readSsg, resetSsg }; } Nuked2608::~Nuked2608() { stopDevice(); } int Nuked2608::startDevice(int clock, int& rateSsg, uint32_t dramSize) { int clockSsg = clock / 4; rateSsg = clockSsg / 8; state_.ssg = PSG_new(clockSsg, rateSsg); if (!state_.ssg) return 0; PSG_setVolumeMode(state_.ssg, 1); // YM2149 volume mode state_.chip = reinterpret_cast(std::calloc(1, sizeof(ym3438_t))); if (!state_.chip) { stopDevice(); return 0; } state_.clock = clock; state_.dramSize = dramSize; OPN2_Reset(state_.chip, clock, &SSG_INTF, &state_, dramSize); return clock / 144; // FM synthesis rate is clock / 2 / 72 } void Nuked2608::stopDevice() { if (state_.chip) { OPN2_Destroy(state_.chip); std::free(state_.chip); state_.chip = nullptr; } if (state_.ssg) { PSG_delete(state_.ssg); state_.ssg = nullptr; } } void Nuked2608::resetDevice() { OPN2_FlushBuffer(state_.chip); OPN2_Reset(state_.chip, state_.clock, &SSG_INTF, &state_, state_.dramSize); } void Nuked2608::writeAddressToPortA(uint8_t address) { OPN2_WriteBuffered(state_.chip, 0, address); } void Nuked2608::writeAddressToPortB(uint8_t address) { OPN2_WriteBuffered(state_.chip, 2, address); } void Nuked2608::writeDataToPortA(uint8_t data) { OPN2_WriteBuffered(state_.chip, 1, data); } void Nuked2608::writeDataToPortB(uint8_t data) { OPN2_WriteBuffered(state_.chip, 3, data); } uint8_t Nuked2608::readData() { return OPN2_Read(state_.chip, 1); } void Nuked2608::updateStream(sample** outputs, int nSamples) { sample* bufl = outputs[STEREO_LEFT]; sample* bufr = outputs[STEREO_RIGHT]; for (int i = 0; i < nSamples; ++i) { sample lr[2]; OPN2_Generate(state_.chip, lr); *bufl++ = lr[0]; *bufr++ = lr[1]; } } void Nuked2608::updateSsgStream(sample** outputs, int nSamples) { if (state_.ssg) { sample* bufl = outputs[STEREO_LEFT]; sample* bufr = outputs[STEREO_RIGHT]; for (int i = 0; i < nSamples; ++i) { int16_t s = PSG_calc(state_.ssg) << 1; *bufl++ = s; *bufr++ = s; } } else { std::fill_n(outputs[STEREO_LEFT], nSamples, 0); std::fill_n(outputs[STEREO_RIGHT], nSamples, 0); } } } BambooTracker-0.6.5/BambooTracker/chip/nuked/nuked_2608.hpp000066400000000000000000000036421476276175200233250ustar00rootroot00000000000000/* * Copyright (C) 2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "../2608_interface.hpp" #include extern "C" { #include "ym3438.h" } #include "emu2149.h" namespace chip { struct Nuked2608State { ym3438_t* chip; PSG* ssg; int clock; uint32_t dramSize; }; class Nuked2608 final : public Ym2608Interface { public: ~Nuked2608() override; int startDevice(int clock, int& rateSsg, uint32_t dramSize) override; void stopDevice() override; void resetDevice() override; void writeAddressToPortA(uint8_t address) override; void writeAddressToPortB(uint8_t address) override; void writeDataToPortA(uint8_t data) override; void writeDataToPortB(uint8_t data) override; uint8_t readData() override; void updateStream(sample** outputs, int nSamples) override; void updateSsgStream(sample** outputs, int nSamples) override; private: Nuked2608State state_; }; } BambooTracker-0.6.5/BambooTracker/chip/nuked/ym3438.c000066400000000000000000001655231476276175200221510ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Alexey Khokholov (Nuke.YKT) * Copyright (C) 2019 Jean Pierre Cimalando * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * * Nuked OPN2-MOD emulator, with OPNA functionality added. * Thanks: * Silicon Pr0n: * Yamaha YM3438 decap and die shot(digshadow). * OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): * OPL2 ROMs. * * version: 1.0.9 * * OPN-MOD additions: * - Jean Pierre Cimalando 2019-04-06: add SSG control interface * - Jean Pierre Cimalando 2019-04-06: add 6-channel FM flag * - Jean Pierre Cimalando 2019-04-06: add ADPCM rhythm channels * - Jean Pierre Cimalando 2019-04-07: raise the channel clipping threshold * - Jean Pierre Cimalando 2020-02-23: add the ADPCM Delta-T */ #include #include #include "ym3438.h" /*OPN-MOD: define the quantization in bits at which channels clip*/ static const unsigned channel_clipbits = 16; /*YM2612 has 9 bits*/ enum { eg_num_attack = 0, eg_num_decay = 1, eg_num_sustain = 2, eg_num_release = 3 }; /* logsin table */ static const Bit16u logsinrom[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 }; /* exp table */ static const Bit16u exprom[256] = { 0x000, 0x003, 0x006, 0x008, 0x00b, 0x00e, 0x011, 0x014, 0x016, 0x019, 0x01c, 0x01f, 0x022, 0x025, 0x028, 0x02a, 0x02d, 0x030, 0x033, 0x036, 0x039, 0x03c, 0x03f, 0x042, 0x045, 0x048, 0x04b, 0x04e, 0x051, 0x054, 0x057, 0x05a, 0x05d, 0x060, 0x063, 0x066, 0x069, 0x06c, 0x06f, 0x072, 0x075, 0x078, 0x07b, 0x07e, 0x082, 0x085, 0x088, 0x08b, 0x08e, 0x091, 0x094, 0x098, 0x09b, 0x09e, 0x0a1, 0x0a4, 0x0a8, 0x0ab, 0x0ae, 0x0b1, 0x0b5, 0x0b8, 0x0bb, 0x0be, 0x0c2, 0x0c5, 0x0c8, 0x0cc, 0x0cf, 0x0d2, 0x0d6, 0x0d9, 0x0dc, 0x0e0, 0x0e3, 0x0e7, 0x0ea, 0x0ed, 0x0f1, 0x0f4, 0x0f8, 0x0fb, 0x0ff, 0x102, 0x106, 0x109, 0x10c, 0x110, 0x114, 0x117, 0x11b, 0x11e, 0x122, 0x125, 0x129, 0x12c, 0x130, 0x134, 0x137, 0x13b, 0x13e, 0x142, 0x146, 0x149, 0x14d, 0x151, 0x154, 0x158, 0x15c, 0x160, 0x163, 0x167, 0x16b, 0x16f, 0x172, 0x176, 0x17a, 0x17e, 0x181, 0x185, 0x189, 0x18d, 0x191, 0x195, 0x199, 0x19c, 0x1a0, 0x1a4, 0x1a8, 0x1ac, 0x1b0, 0x1b4, 0x1b8, 0x1bc, 0x1c0, 0x1c4, 0x1c8, 0x1cc, 0x1d0, 0x1d4, 0x1d8, 0x1dc, 0x1e0, 0x1e4, 0x1e8, 0x1ec, 0x1f0, 0x1f5, 0x1f9, 0x1fd, 0x201, 0x205, 0x209, 0x20e, 0x212, 0x216, 0x21a, 0x21e, 0x223, 0x227, 0x22b, 0x230, 0x234, 0x238, 0x23c, 0x241, 0x245, 0x249, 0x24e, 0x252, 0x257, 0x25b, 0x25f, 0x264, 0x268, 0x26d, 0x271, 0x276, 0x27a, 0x27f, 0x283, 0x288, 0x28c, 0x291, 0x295, 0x29a, 0x29e, 0x2a3, 0x2a8, 0x2ac, 0x2b1, 0x2b5, 0x2ba, 0x2bf, 0x2c4, 0x2c8, 0x2cd, 0x2d2, 0x2d6, 0x2db, 0x2e0, 0x2e5, 0x2e9, 0x2ee, 0x2f3, 0x2f8, 0x2fd, 0x302, 0x306, 0x30b, 0x310, 0x315, 0x31a, 0x31f, 0x324, 0x329, 0x32e, 0x333, 0x338, 0x33d, 0x342, 0x347, 0x34c, 0x351, 0x356, 0x35b, 0x360, 0x365, 0x36a, 0x370, 0x375, 0x37a, 0x37f, 0x384, 0x38a, 0x38f, 0x394, 0x399, 0x39f, 0x3a4, 0x3a9, 0x3ae, 0x3b4, 0x3b9, 0x3bf, 0x3c4, 0x3c9, 0x3cf, 0x3d4, 0x3da, 0x3df, 0x3e4, 0x3ea, 0x3ef, 0x3f5, 0x3fa }; /* Note table */ static const Bit32u fn_note[16] = { 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 3, 3, 3, 3, 3 }; /* Envelope generator */ static const Bit32u eg_stephi[4][4] = { { 0, 0, 0, 0 }, { 1, 0, 0, 0 }, { 1, 0, 1, 0 }, { 1, 1, 1, 0 } }; static const Bit8u eg_am_shift[4] = { 7, 3, 1, 0 }; /* Phase generator */ static const Bit32u pg_detune[8] = { 16, 17, 19, 20, 22, 24, 27, 29 }; static const Bit32u pg_lfo_sh1[8][8] = { { 7, 7, 7, 7, 7, 7, 7, 7 }, { 7, 7, 7, 7, 7, 7, 7, 7 }, { 7, 7, 7, 7, 7, 7, 1, 1 }, { 7, 7, 7, 7, 1, 1, 1, 1 }, { 7, 7, 7, 1, 1, 1, 1, 0 }, { 7, 7, 1, 1, 0, 0, 0, 0 }, { 7, 7, 1, 1, 0, 0, 0, 0 }, { 7, 7, 1, 1, 0, 0, 0, 0 } }; static const Bit32u pg_lfo_sh2[8][8] = { { 7, 7, 7, 7, 7, 7, 7, 7 }, { 7, 7, 7, 7, 2, 2, 2, 2 }, { 7, 7, 7, 2, 2, 2, 7, 7 }, { 7, 7, 2, 2, 7, 7, 2, 2 }, { 7, 7, 2, 7, 7, 7, 2, 7 }, { 7, 7, 7, 2, 7, 7, 2, 1 }, { 7, 7, 7, 2, 7, 7, 2, 1 }, { 7, 7, 7, 2, 7, 7, 2, 1 } }; /* Address decoder */ static const Bit32u op_offset[12] = { 0x000, /* Ch1 OP1/OP2 */ 0x001, /* Ch2 OP1/OP2 */ 0x002, /* Ch3 OP1/OP2 */ 0x100, /* Ch4 OP1/OP2 */ 0x101, /* Ch5 OP1/OP2 */ 0x102, /* Ch6 OP1/OP2 */ 0x004, /* Ch1 OP3/OP4 */ 0x005, /* Ch2 OP3/OP4 */ 0x006, /* Ch3 OP3/OP4 */ 0x104, /* Ch4 OP3/OP4 */ 0x105, /* Ch5 OP3/OP4 */ 0x106 /* Ch6 OP3/OP4 */ }; static const Bit32u ch_offset[6] = { 0x000, /* Ch1 */ 0x001, /* Ch2 */ 0x002, /* Ch3 */ 0x100, /* Ch4 */ 0x101, /* Ch5 */ 0x102 /* Ch6 */ }; /* LFO */ static const Bit32u lfo_cycles[8] = { 108, 77, 71, 67, 62, 44, 8, 5 }; /* FM algorithm */ static const Bit32u fm_algorithm[4][6][8] = { { { 1, 1, 1, 1, 1, 1, 1, 1 }, /* OP1_0 */ { 1, 1, 1, 1, 1, 1, 1, 1 }, /* OP1_1 */ { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP2 */ { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ { 0, 0, 0, 0, 0, 0, 0, 1 } /* Out */ }, { { 0, 1, 0, 0, 0, 1, 0, 0 }, /* OP1_0 */ { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP1_1 */ { 1, 1, 1, 0, 0, 0, 0, 0 }, /* OP2 */ { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ { 0, 0, 0, 0, 0, 1, 1, 1 } /* Out */ }, { { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP1_0 */ { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP1_1 */ { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP2 */ { 1, 0, 0, 1, 1, 1, 1, 0 }, /* Last operator */ { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ { 0, 0, 0, 0, 1, 1, 1, 1 } /* Out */ }, { { 0, 0, 1, 0, 0, 1, 0, 0 }, /* OP1_0 */ { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP1_1 */ { 0, 0, 0, 1, 0, 0, 0, 0 }, /* OP2 */ { 1, 1, 0, 1, 1, 0, 0, 0 }, /* Last operator */ { 0, 0, 1, 0, 0, 0, 0, 0 }, /* Last operator */ { 1, 1, 1, 1, 1, 1, 1, 1 } /* Out */ } }; /*OPN-MOD: ADPCM jedi table*/ static const Bit16s adpcm_jedi_table[49 * 16] = { 2, 6, 10, 14, 18, 22, 26, 30, -2, -6, -10, -14, -18, -22, -26, -30, 2, 6, 10, 14, 19, 23, 27, 31, -2, -6, -10, -14, -19, -23, -27, -31, 2, 7, 11, 16, 21, 26, 30, 35, -2, -7, -11, -16, -21, -26, -30, -35, 2, 7, 13, 18, 23, 28, 34, 39, -2, -7, -13, -18, -23, -28, -34, -39, 2, 8, 14, 20, 25, 31, 37, 43, -2, -8, -14, -20, -25, -31, -37, -43, 3, 9, 15, 21, 28, 34, 40, 46, -3, -9, -15, -21, -28, -34, -40, -46, 3, 10, 17, 24, 31, 38, 45, 52, -3, -10, -17, -24, -31, -38, -45, -52, 3, 11, 19, 27, 34, 42, 50, 58, -3, -11, -19, -27, -34, -42, -50, -58, 4, 12, 21, 29, 38, 46, 55, 63, -4, -12, -21, -29, -38, -46, -55, -63, 4, 13, 23, 32, 41, 50, 60, 69, -4, -13, -23, -32, -41, -50, -60, -69, 5, 15, 25, 35, 46, 56, 66, 76, -5, -15, -25, -35, -46, -56, -66, -76, 5, 16, 28, 39, 50, 61, 73, 84, -5, -16, -28, -39, -50, -61, -73, -84, 6, 18, 31, 43, 56, 68, 81, 93, -6, -18, -31, -43, -56, -68, -81, -93, 6, 20, 34, 48, 61, 75, 89, 103, -6, -20, -34, -48, -61, -75, -89, -103, 7, 22, 37, 52, 67, 82, 97, 112, -7, -22, -37, -52, -67, -82, -97, -112, 8, 24, 41, 57, 74, 90, 107, 123, -8, -24, -41, -57, -74, -90, -107, -123, 9, 27, 45, 63, 82, 100, 118, 136, -9, -27, -45, -63, -82, -100, -118, -136, 10, 30, 50, 70, 90, 110, 130, 150, -10, -30, -50, -70, -90, -110, -130, -150, 11, 33, 55, 77, 99, 121, 143, 165, -11, -33, -55, -77, -99, -121, -143, -165, 12, 36, 60, 84, 109, 133, 157, 181, -12, -36, -60, -84, -109, -133, -157, -181, 13, 40, 66, 93, 120, 147, 173, 200, -13, -40, -66, -93, -120, -147, -173, -200, 14, 44, 73, 103, 132, 162, 191, 221, -14, -44, -73, -103, -132, -162, -191, -221, 16, 48, 81, 113, 146, 178, 211, 243, -16, -48, -81, -113, -146, -178, -211, -243, 17, 53, 89, 125, 160, 196, 232, 268, -17, -53, -89, -125, -160, -196, -232, -268, 19, 58, 98, 137, 176, 215, 255, 294, -19, -58, -98, -137, -176, -215, -255, -294, 21, 64, 108, 151, 194, 237, 281, 324, -21, -64, -108, -151, -194, -237, -281, -324, 23, 71, 118, 166, 213, 261, 308, 356, -23, -71, -118, -166, -213, -261, -308, -356, 26, 78, 130, 182, 235, 287, 339, 391, -26, -78, -130, -182, -235, -287, -339, -391, 28, 86, 143, 201, 258, 316, 373, 431, -28, -86, -143, -201, -258, -316, -373, -431, 31, 94, 158, 221, 284, 347, 411, 474, -31, -94, -158, -221, -284, -347, -411, -474, 34, 104, 174, 244, 313, 383, 453, 523, -34, -104, -174, -244, -313, -383, -453, -523, 38, 115, 191, 268, 345, 422, 498, 575, -38, -115, -191, -268, -345, -422, -498, -575, 42, 126, 210, 294, 379, 463, 547, 631, -42, -126, -210, -294, -379, -463, -547, -631, 46, 139, 231, 324, 417, 510, 602, 695, -46, -139, -231, -324, -417, -510, -602, -695, 51, 153, 255, 357, 459, 561, 663, 765, -51, -153, -255, -357, -459, -561, -663, -765, 56, 168, 280, 392, 505, 617, 729, 841, -56, -168, -280, -392, -505, -617, -729, -841, 61, 185, 308, 432, 555, 679, 802, 926, -61, -185, -308, -432, -555, -679, -802, -926, 68, 204, 340, 476, 612, 748, 884, 1020, -68, -204, -340, -476, -612, -748, -884, -1020, 74, 224, 373, 523, 672, 822, 971, 1121, -74, -224, -373, -523, -672, -822, -971, -1121, 82, 246, 411, 575, 740, 904, 1069, 1233, -82, -246, -411, -575, -740, -904, -1069, -1233, 90, 271, 452, 633, 814, 995, 1176, 1357, -90, -271, -452, -633, -814, -995, -1176, -1357, 99, 298, 497, 696, 895, 1094, 1293, 1492, -99, -298, -497, -696, -895, -1094, -1293, -1492, 109, 328, 547, 766, 985, 1204, 1423, 1642, -109, -328, -547, -766, -985, -1204, -1423, -1642, 120, 361, 601, 842, 1083, 1324, 1564, 1805, -120, -361, -601, -842, -1083, -1324, -1564, -1805, 132, 397, 662, 927, 1192, 1457, 1722, 1987, -132, -397, -662, -927, -1192, -1457, -1722, -1987, 145, 437, 728, 1020, 1311, 1603, 1894, 2186, -145, -437, -728, -1020, -1311, -1603, -1894, -2186, 160, 480, 801, 1121, 1442, 1762, 2083, 2403, -160, -480, -801, -1121, -1442, -1762, -2083, -2403, 176, 529, 881, 1234, 1587, 1940, 2292, 2645, -176, -529, -881, -1234, -1587, -1940, -2292, -2645, 194, 582, 970, 1358, 1746, 2134, 2522, 2910, -194, -582, -970, -1358, -1746, -2134, -2522, -2910 }; /*OPN-MOD: ADPCM table*/ extern const unsigned int YM2608_ADPCM_ROM_addr[2*6]; extern const unsigned char YM2608_ADPCM_ROM[0x2000]; /*OPN-MOD: ADPCM constants*/ static const Bitu adpcm_shift = 16; static const int adpcm_step_inc[8] = { -1*16, -1*16, -1*16, -1*16, 2*16, 5*16, 7*16, 9*16 }; static Bit32u chip_type = ym3438_mode_readmode; /*OPN-MOD: update ADPCM volume*/ void OPNmod_RhythmUpdateVolume(ym3438_t *chip, Bit32u channel) { Bit8u volume = chip->rhythm_tl + chip->rhythm_level[channel]; /* volume formula from MAME OPN */ if (volume >= 63) /* This is correct, 63 = quiet */ { chip->rhythm_vol_mul[channel] = 0; chip->rhythm_vol_shift[channel] = 0; } else { chip->rhythm_vol_mul[channel] = 15 - (volume & 7); /* so called 0.75 dB */ chip->rhythm_vol_shift[channel] = 1 + (volume >> 3); /* Yamaha engineers used the approximation: each -6 dB is close to divide by two (shift right) */ } } void OPN2_DoIO(ym3438_t *chip) { /* Write signal check */ chip->write_a_en = (chip->write_a & 0x03) == 0x01; chip->write_d_en = (chip->write_d & 0x03) == 0x01; chip->write_a <<= 1; chip->write_d <<= 1; /* Busy counter */ chip->busy = chip->write_busy; chip->write_busy_cnt += chip->write_busy; chip->write_busy = (chip->write_busy && !(chip->write_busy_cnt >> 5)) || chip->write_d_en; chip->write_busy_cnt &= 0x1f; } void OPN2_DoRegWrite(ym3438_t *chip) { Bit32u i; Bit32u slot = chip->cycles % 12; Bit32u address; Bit32u channel = chip->channel; /* Update registers */ if (chip->write_fm_data) { /* Slot */ if (op_offset[slot] == (Bit32u)(chip->address & 0x107)) { if (chip->address & 0x08) { /* OP2, OP4 */ slot += 12; } address = chip->address & 0xf0; switch (address) { case 0x30: /* DT, MULTI */ chip->multi[slot] = chip->data & 0x0f; if (!chip->multi[slot]) { chip->multi[slot] = 1; } else { chip->multi[slot] <<= 1; } chip->dt[slot] = (chip->data >> 4) & 0x07; break; case 0x40: /* TL */ chip->tl[slot] = chip->data & 0x7f; break; case 0x50: /* KS, AR */ chip->ar[slot] = chip->data & 0x1f; chip->ks[slot] = (chip->data >> 6) & 0x03; break; case 0x60: /* AM, DR */ chip->dr[slot] = chip->data & 0x1f; chip->am[slot] = (chip->data >> 7) & 0x01; break; case 0x70: /* SR */ chip->sr[slot] = chip->data & 0x1f; break; case 0x80: /* SL, RR */ chip->rr[slot] = chip->data & 0x0f; chip->sl[slot] = (chip->data >> 4) & 0x0f; chip->sl[slot] |= (chip->sl[slot] + 1) & 0x10; break; case 0x90: /* SSG-EG */ chip->ssg_eg[slot] = chip->data & 0x0f; break; default: break; } } /* Channel */ if (ch_offset[channel] == (Bit32u)(chip->address & 0x103)) { address = chip->address & 0xfc; switch (address) { case 0xa0: chip->fnum[channel] = (chip->data & 0xff) | ((chip->reg_a4 & 0x07) << 8); chip->block[channel] = (chip->reg_a4 >> 3) & 0x07; chip->kcode[channel] = (Bit8u)((chip->block[channel] << 2) | fn_note[chip->fnum[channel] >> 7]); break; case 0xa4: chip->reg_a4 = chip->data & 0xff; break; case 0xa8: chip->fnum_3ch[channel] = (chip->data & 0xff) | ((chip->reg_ac & 0x07) << 8); chip->block_3ch[channel] = (chip->reg_ac >> 3) & 0x07; chip->kcode_3ch[channel] = (chip->block_3ch[channel] << 2) | fn_note[chip->fnum_3ch[channel] >> 7]; break; case 0xac: chip->reg_ac = chip->data & 0xff; break; case 0xb0: chip->connect[channel] = chip->data & 0x07; chip->fb[channel] = (chip->data >> 3) & 0x07; break; case 0xb4: chip->pms[channel] = chip->data & 0x07; chip->ams[channel] = (chip->data >> 4) & 0x03; chip->pan_l[channel] = (chip->data >> 7) & 0x01; chip->pan_r[channel] = (chip->data >> 6) & 0x01; break; default: break; } } } if (chip->write_a_en || chip->write_d_en) { /* Data */ if (chip->write_a_en) { chip->write_fm_data = 0; } if (chip->write_d_en) { chip->write_fm_data = 1; } /* Address */ if (chip->write_a_en) { /*OPN-MOD: store address for both FM/SSG modes (for PSG read) */ chip->address = chip->write_data; if ((chip->write_data & 0xf0) != 0x00) { /* FM Write */ chip->write_fm_address = 1; } else { /* SSG write */ chip->write_fm_address = 0; } } /* FM Mode */ /* Data */ if (chip->write_d_en && (chip->write_data & 0x100) == 0) { switch (chip->write_fm_mode_a) { /*OPN-MOD: Rhythm key on/off*/ case 0x10: if ((chip->write_data & 0x80) == 0) { for (i = 0; i < 6; ++i) { if (chip->write_data & (1 << i)) { chip->rhythm_key[i] = 1; chip->rhythm_addr[i] = YM2608_ADPCM_ROM_addr[2 * i] << 1; chip->rhythm_now_step[i] = 0; chip->rhythm_adpcm_step[i] = 0; chip->rhythm_adpcm_acc[i] = 0; } } } else { for (i = 0; i < 6; ++i) { if (chip->write_data & (1 << i)) { chip->rhythm_key[i] = 0; chip->rhythml[i] = 0; chip->rhythmr[i] = 0; } } } break; /*OPN-MOD: Rhythm total level*/ case 0x11: chip->rhythm_tl = ~chip->write_data & 63; for (i = 0; i < 6; ++i) { OPNmod_RhythmUpdateVolume(chip, i); } break; /*OPN-MOD: Rhythm instrument pan/level*/ case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: i = chip->write_fm_mode_a - 0x18; chip->rhythm_pan[i] = (chip->write_data >> 6) & 3; chip->rhythm_level[i] = ~chip->write_data & 31; OPNmod_RhythmUpdateVolume(chip, i); break; case 0x21: /* LSI test 1 */ for (i = 0; i < 8; i++) { chip->mode_test_21[i] = (chip->write_data >> i) & 0x01; } break; case 0x22: /* LFO control */ if ((chip->write_data >> 3) & 0x01) { chip->lfo_en = 0x7f; } else { chip->lfo_en = 0; } chip->lfo_freq = chip->write_data & 0x07; break; case 0x24: /* Timer A */ chip->timer_a_reg &= 0x03; chip->timer_a_reg |= (chip->write_data & 0xff) << 2; break; case 0x25: chip->timer_a_reg &= 0x3fc; chip->timer_a_reg |= chip->write_data & 0x03; break; case 0x26: /* Timer B */ chip->timer_b_reg = chip->write_data & 0xff; break; case 0x27: /* CSM, Timer control */ chip->mode_ch3 = (chip->write_data & 0xc0) >> 6; chip->mode_csm = chip->mode_ch3 == 2; chip->timer_a_load = chip->write_data & 0x01; chip->timer_a_enable = (chip->write_data >> 2) & 0x01; chip->timer_a_reset = (chip->write_data >> 4) & 0x01; chip->timer_b_load = (chip->write_data >> 1) & 0x01; chip->timer_b_enable = (chip->write_data >> 3) & 0x01; chip->timer_b_reset = (chip->write_data >> 5) & 0x01; break; case 0x28: /* Key on/off */ for (i = 0; i < 4; i++) { chip->mode_kon_operator[i] = (chip->write_data >> (4 + i)) & 0x01; } if ((chip->write_data & 0x03) == 0x03) { /* Invalid address */ chip->mode_kon_channel = 0xff; } else { /*OPN-MOD: select according to 6 FM channel flag*/ chip->mode_kon_channel = chip->write_data & 0x03; if (chip->mode_fm6ch && (chip->write_data & 0x04)) { chip->mode_kon_channel += 3; } } break; case 0x29: /*OPN-MOD: set 6 FM channel flag*/ chip->mode_fm6ch = chip->write_data & 0x80; break; case 0x2a: /* DAC data */ chip->dacdata &= 0x01; chip->dacdata |= (chip->write_data ^ 0x80) << 1; break; case 0x2b: /* DAC enable */ chip->dacen = chip->write_data >> 7; break; case 0x2c: /* LSI test 2 */ for (i = 0; i < 8; i++) { chip->mode_test_2c[i] = (chip->write_data >> i) & 0x01; } chip->dacdata &= 0x1fe; chip->dacdata |= chip->mode_test_2c[3]; chip->eg_custom_timer = !chip->mode_test_2c[7] && chip->mode_test_2c[6]; break; default: /*OPN-MOD: write $00-$0F to PSG*/ if (chip->write_fm_mode_a < 0x10) { chip->psg->Write(chip->psgdata, 0, chip->write_fm_mode_a); chip->psg->Write(chip->psgdata, 1, chip->write_data); } break; } } else if (chip->write_d_en && (chip->write_data & 0x100) == 0x100) { /*OPN-Mod*/ switch (chip->write_fm_mode_a & 0xf0) { case 0x00: if (chip->write_fm_mode_a == 0x0e) { /* write to DAC data (unimplemented) */ } else if (chip->write_fm_mode_a != 0x0f) { YM_DELTAT_ADPCM_Write(&chip->deltaT, chip->write_fm_mode_a, chip->write_data & 0xff); } break; case 0x10: /* IRQ flag control (unimplemented) */ break; } } /* Address */ if (chip->write_a_en) { chip->write_fm_mode_a = chip->write_data & 0x1ff; } } if (chip->write_fm_data) { chip->data = chip->write_data & 0xff; } } void OPN2_PhaseCalcIncrement(ym3438_t *chip) { Bit32u chan = chip->channel; Bit32u slot = chip->cycles; Bit32u fnum = chip->pg_fnum; Bit32u fnum_h = fnum >> 4; Bit32u fm; Bit32u basefreq; Bit8u lfo = chip->lfo_pm; Bit8u lfo_l = lfo & 0x0f; Bit8u pms = chip->pms[chan]; Bit8u dt = chip->dt[slot]; Bit8u dt_l = dt & 0x03; Bit8u detune = 0; Bit8u block, note; Bit8u sum, sum_h, sum_l; Bit8u kcode = chip->pg_kcode; fnum <<= 1; /* Apply LFO */ if (lfo_l & 0x08) { lfo_l ^= 0x0f; } fm = (fnum_h >> pg_lfo_sh1[pms][lfo_l]) + (fnum_h >> pg_lfo_sh2[pms][lfo_l]); if (pms > 5) { fm <<= pms - 5; } fm >>= 2; if (lfo & 0x10) { fnum -= fm; } else { fnum += fm; } fnum &= 0xfff; basefreq = (fnum << chip->pg_block) >> 2; /* Apply detune */ if (dt_l) { if (kcode > 0x1c) { kcode = 0x1c; } block = kcode >> 2; note = kcode & 0x03; sum = block + 9 + ((dt_l == 3) | (dt_l & 0x02)); sum_h = sum >> 1; sum_l = sum & 0x01; detune = pg_detune[(sum_l << 2) | note] >> (9 - sum_h); } if (dt & 0x04) { basefreq -= detune; } else { basefreq += detune; } basefreq &= 0x1ffff; chip->pg_inc[slot] = (basefreq * chip->multi[slot]) >> 1; chip->pg_inc[slot] &= 0xfffff; } void OPN2_PhaseGenerate(ym3438_t *chip) { Bit32u slot; /* Mask increment */ slot = (chip->cycles + 20) % 24; if (chip->pg_reset[slot]) { chip->pg_inc[slot] = 0; } /* Phase step */ slot = (chip->cycles + 19) % 24; chip->pg_phase[slot] += chip->pg_inc[slot]; chip->pg_phase[slot] &= 0xfffff; if (chip->pg_reset[slot] || chip->mode_test_21[3]) { chip->pg_phase[slot] = 0; } } void OPN2_EnvelopeSSGEG(ym3438_t *chip) { Bit32u slot = chip->cycles; Bit8u direction = 0; chip->eg_ssg_pgrst_latch[slot] = 0; chip->eg_ssg_repeat_latch[slot] = 0; chip->eg_ssg_hold_up_latch[slot] = 0; chip->eg_ssg_inv[slot] = 0; if (chip->ssg_eg[slot] & 0x08) { direction = chip->eg_ssg_dir[slot]; if (chip->eg_level[slot] & 0x200) { /* Reset */ if ((chip->ssg_eg[slot] & 0x03) == 0x00) { chip->eg_ssg_pgrst_latch[slot] = 1; } /* Repeat */ if ((chip->ssg_eg[slot] & 0x01) == 0x00) { chip->eg_ssg_repeat_latch[slot] = 1; } /* Inverse */ if ((chip->ssg_eg[slot] & 0x03) == 0x02) { direction ^= 1; } if ((chip->ssg_eg[slot] & 0x03) == 0x03) { direction = 1; } } /* Hold up */ if (chip->eg_kon_latch[slot] && ((chip->ssg_eg[slot] & 0x07) == 0x05 || (chip->ssg_eg[slot] & 0x07) == 0x03)) { chip->eg_ssg_hold_up_latch[slot] = 1; } direction &= chip->eg_kon[slot]; chip->eg_ssg_inv[slot] = (chip->eg_ssg_dir[slot] ^ ((chip->ssg_eg[slot] >> 2) & 0x01)) & chip->eg_kon[slot]; } chip->eg_ssg_dir[slot] = direction; chip->eg_ssg_enable[slot] = (chip->ssg_eg[slot] >> 3) & 0x01; } void OPN2_EnvelopeADSR(ym3438_t *chip) { Bit32u slot = (chip->cycles + 22) % 24; Bit8u nkon = chip->eg_kon_latch[slot]; Bit8u okon = chip->eg_kon[slot]; Bit8u kon_event; Bit8u koff_event; Bit8u eg_off; Bit16s level; Bit16s nextlevel = 0; Bit16s ssg_level; Bit8u nextstate = chip->eg_state[slot]; Bit16s inc = 0; chip->eg_read[0] = chip->eg_read_inc; chip->eg_read_inc = chip->eg_inc > 0; /* Reset phase generator */ chip->pg_reset[slot] = (nkon && !okon) || chip->eg_ssg_pgrst_latch[slot]; /* KeyOn/Off */ kon_event = (nkon && !okon) || (okon && chip->eg_ssg_repeat_latch[slot]); koff_event = okon && !nkon; ssg_level = level = (Bit16s)chip->eg_level[slot]; if (chip->eg_ssg_inv[slot]) { /* Inverse */ ssg_level = 512 - level; ssg_level &= 0x3ff; } if (koff_event) { level = ssg_level; } if (chip->eg_ssg_enable[slot]) { eg_off = level >> 9; } else { eg_off = (level & 0x3f0) == 0x3f0; } nextlevel = level; if (kon_event) { nextstate = eg_num_attack; /* Instant attack */ if (chip->eg_ratemax) { nextlevel = 0; } else if (chip->eg_state[slot] == eg_num_attack && level != 0 && chip->eg_inc && nkon) { inc = (~level << chip->eg_inc) >> 5; } } else { switch (chip->eg_state[slot]) { case eg_num_attack: if (level == 0) { nextstate = eg_num_decay; } else if(chip->eg_inc && !chip->eg_ratemax && nkon) { inc = (~level << chip->eg_inc) >> 5; } break; case eg_num_decay: if ((level >> 5) == chip->eg_sl[1]) { nextstate = eg_num_sustain; } else if (!eg_off && chip->eg_inc) { inc = 1 << (chip->eg_inc - 1); if (chip->eg_ssg_enable[slot]) { inc <<= 2; } } break; case eg_num_sustain: case eg_num_release: if (!eg_off && chip->eg_inc) { inc = 1 << (chip->eg_inc - 1); if (chip->eg_ssg_enable[slot]) { inc <<= 2; } } break; default: break; } if (!nkon) { nextstate = eg_num_release; } } if (chip->eg_kon_csm[slot]) { nextlevel |= chip->eg_tl[1] << 3; } /* Envelope off */ if (!kon_event && !chip->eg_ssg_hold_up_latch[slot] && chip->eg_state[slot] != eg_num_attack && eg_off) { nextstate = eg_num_release; nextlevel = 0x3ff; } nextlevel += inc; chip->eg_kon[slot] = chip->eg_kon_latch[slot]; chip->eg_level[slot] = (Bit16u)nextlevel & 0x3ff; chip->eg_state[slot] = nextstate; } void OPN2_EnvelopePrepare(ym3438_t *chip) { Bit8u rate; Bit8u sum; Bit8u inc = 0; Bit32u slot = chip->cycles; Bit8u rate_sel; /* Prepare increment */ rate = (chip->eg_rate << 1) + chip->eg_ksv; if (rate > 0x3f) { rate = 0x3f; } sum = ((rate >> 2) + chip->eg_shift_lock) & 0x0f; if (chip->eg_rate != 0 && chip->eg_quotient == 2) { if (rate < 48) { switch (sum) { case 12: inc = 1; break; case 13: inc = (rate >> 1) & 0x01; break; case 14: inc = rate & 0x01; break; default: break; } } else { inc = eg_stephi[rate & 0x03][chip->eg_timer_low_lock] + (rate >> 2) - 11; if (inc > 4) { inc = 4; } } } chip->eg_inc = inc; chip->eg_ratemax = (rate >> 1) == 0x1f; /* Prepare rate & ksv */ rate_sel = chip->eg_state[slot]; if ((chip->eg_kon[slot] && chip->eg_ssg_repeat_latch[slot]) || (!chip->eg_kon[slot] && chip->eg_kon_latch[slot])) { rate_sel = eg_num_attack; } switch (rate_sel) { case eg_num_attack: chip->eg_rate = chip->ar[slot]; break; case eg_num_decay: chip->eg_rate = chip->dr[slot]; break; case eg_num_sustain: chip->eg_rate = chip->sr[slot]; break; case eg_num_release: chip->eg_rate = (chip->rr[slot] << 1) | 0x01; break; default: break; } chip->eg_ksv = chip->pg_kcode >> (chip->ks[slot] ^ 0x03); if (chip->am[slot]) { chip->eg_lfo_am = chip->lfo_am >> eg_am_shift[chip->ams[chip->channel]]; } else { chip->eg_lfo_am = 0; } /* Delay TL & SL value */ chip->eg_tl[1] = chip->eg_tl[0]; chip->eg_tl[0] = chip->tl[slot]; chip->eg_sl[1] = chip->eg_sl[0]; chip->eg_sl[0] = chip->sl[slot]; } void OPN2_EnvelopeGenerate(ym3438_t *chip) { Bit32u slot = (chip->cycles + 23) % 24; Bit16u level; level = chip->eg_level[slot]; if (chip->eg_ssg_inv[slot]) { /* Inverse */ level = 512 - level; } if (chip->mode_test_21[5]) { level = 0; } level &= 0x3ff; /* Apply AM LFO */ level += chip->eg_lfo_am; /* Apply TL */ if (!(chip->mode_csm && chip->channel == 2 + 1)) { level += chip->eg_tl[0] << 3; } if (level > 0x3ff) { level = 0x3ff; } chip->eg_out[slot] = level; } void OPN2_UpdateLFO(ym3438_t *chip) { if ((chip->lfo_quotient & lfo_cycles[chip->lfo_freq]) == lfo_cycles[chip->lfo_freq]) { chip->lfo_quotient = 0; chip->lfo_cnt++; } else { chip->lfo_quotient += chip->lfo_inc; } chip->lfo_cnt &= chip->lfo_en; } void OPN2_FMPrepare(ym3438_t *chip) { Bit32u slot = (chip->cycles + 6) % 24; Bit32u channel = chip->channel; Bit16s mod, mod1, mod2; Bit32u op = slot / 6; Bit8u connect = chip->connect[channel]; Bit32u prevslot = (chip->cycles + 18) % 24; /* Calculate modulation */ mod1 = mod2 = 0; if (fm_algorithm[op][0][connect]) { mod2 |= chip->fm_op1[channel][0]; } if (fm_algorithm[op][1][connect]) { mod1 |= chip->fm_op1[channel][1]; } if (fm_algorithm[op][2][connect]) { mod1 |= chip->fm_op2[channel]; } if (fm_algorithm[op][3][connect]) { mod2 |= chip->fm_out[prevslot]; } if (fm_algorithm[op][4][connect]) { mod1 |= chip->fm_out[prevslot]; } mod = mod1 + mod2; if (op == 0) { /* Feedback */ mod = mod >> (10 - chip->fb[channel]); if (!chip->fb[channel]) { mod = 0; } } else { mod >>= 1; } chip->fm_mod[slot] = mod; slot = (chip->cycles + 18) % 24; /* OP1 */ if (slot / 6 == 0) { chip->fm_op1[channel][1] = chip->fm_op1[channel][0]; chip->fm_op1[channel][0] = chip->fm_out[slot]; } /* OP2 */ if (slot / 6 == 2) { chip->fm_op2[channel] = chip->fm_out[slot]; } } void OPN2_ChGenerate(ym3438_t *chip) { Bit32u slot = (chip->cycles + 18) % 24; Bit32u channel = chip->channel; Bit32u op = slot / 6; Bit32u test_dac = chip->mode_test_2c[5]; Bit16s acc = chip->ch_acc[channel]; Bit16s add = test_dac; Bit16s sum = 0; Bit16s channel_maxsample = (1 << (channel_clipbits - 1)) - 1; Bit16s channel_minsample = -(1 << (channel_clipbits - 1)); if (op == 0 && !test_dac) { acc = 0; } if (fm_algorithm[op][5][chip->connect[channel]] && !test_dac) { /*OPN-MOD: drop 1 bit (YM2608) instead of 5 (YM2612)*/ add += chip->fm_out[slot] >> 1; } sum = acc + add; /* Clamp */ /*Bit16s channel_maxsample = (1 << (channel_clipbits - 1)) - 1; Bit16s channel_minsample = -(1 << (channel_clipbits - 1));*/ if (sum > channel_maxsample) { sum = channel_maxsample; } else if(sum < channel_minsample) { sum = channel_minsample; } if (op == 0 || test_dac) { chip->ch_out[channel] = chip->ch_acc[channel]; } chip->ch_acc[channel] = sum; } void OPN2_ChOutput(ym3438_t *chip) { Bit32u cycles = chip->cycles; Bit32u slot = chip->cycles; Bit32u channel = chip->channel; Bit32u test_dac = chip->mode_test_2c[5]; Bit16s out; Bit16s sign; Bit32u out_en; chip->ch_read = chip->ch_lock; if (slot < 12) { /* Ch 4,5,6 */ channel++; } if ((cycles & 3) == 0) { if (!test_dac) { /* Lock value */ chip->ch_lock = chip->ch_out[channel]; } chip->ch_lock_l = chip->pan_l[channel]; chip->ch_lock_r = chip->pan_r[channel]; } /* Ch 6 */ if (((cycles >> 2) == 1 && chip->dacen) || test_dac) { out = (Bit16s)chip->dacdata; out <<= 7; out >>= 7; } else { out = chip->ch_lock; } chip->mol = 0; chip->mor = 0; if (chip_type & ym3438_mode_ym2612) { out_en = ((cycles & 3) == 3) || test_dac; /* YM2612 DAC emulation(not verified) */ sign = out >> 8; if (out >= 0) { out++; sign++; } if (chip->ch_lock_l && out_en) { chip->mol = out; } else { chip->mol = sign; } if (chip->ch_lock_r && out_en) { chip->mor = out; } else { chip->mor = sign; } /* Amplify signal */ chip->mol *= 3; chip->mor *= 3; } else { out_en = ((cycles & 3) != 0) || test_dac; if (chip->ch_lock_l && out_en) { chip->mol = out; } if (chip->ch_lock_r && out_en) { chip->mor = out; } } } void OPN2_FMGenerate(ym3438_t *chip) { Bit32u slot = (chip->cycles + 19) % 24; /* Calculate phase */ Bit16u phase = (chip->fm_mod[slot] + (chip->pg_phase[slot] >> 10)) & 0x3ff; Bit16u quarter; Bit16u level; Bit16s output; if (phase & 0x100) { quarter = (phase ^ 0xff) & 0xff; } else { quarter = phase & 0xff; } level = logsinrom[quarter]; /* Apply envelope */ level += chip->eg_out[slot] << 2; /* Transform */ if (level > 0x1fff) { level = 0x1fff; } output = ((exprom[(level & 0xff) ^ 0xff] | 0x400) << 2) >> (level >> 8); if (phase & 0x200) { output = ((~output) ^ (chip->mode_test_21[4] << 13)) + 1; } else { output = output ^ (chip->mode_test_21[4] << 13); } output <<= 2; output >>= 2; chip->fm_out[slot] = output; } /*OPN-MOD: generate ADPCM rhythm*/ void OPNmod_RhythmGenerate(ym3438_t *chip) { Bit32u channel = chip->channel; Bit32s out = 0; Bit8u panl = 0; Bit8u panr = 0; Bit32u step; Bit8u data; Bit16u end = YM2608_ADPCM_ROM_addr[2 * channel + 1] << 1; if (chip->cycles < 6 && chip->rhythm_key[channel]) { /*Bit32u step; Bit8u data; Bit16u end = YM2608_ADPCM_ROM_addr[2 * channel + 1] << 1;*/ panl = chip->rhythm_pan[channel] & 2; panr = chip->rhythm_pan[channel] & 1; /*from MAME*/ chip->rhythm_now_step[channel] += chip->rhythm_step[channel]; if (chip->rhythm_now_step[channel] >= (1u << adpcm_shift)) { step = chip->rhythm_now_step[channel] >> adpcm_shift; chip->rhythm_now_step[channel] &= (1 << adpcm_shift) - 1; do { /* end check */ /* 11-06-2001 JB: corrected comparison. Was > instead of == */ /* YM2610 checks lower 20 bits only, the 4 MSB bits are sample bank */ /* Here we use 1<<21 to compensate for nibble calculations */ if ((chip->rhythm_addr[channel] & ((1 << 21) - 1)) == (end & ((1 << 21) - 1))) { chip->rhythm_key[channel] = 0; goto end; } if (chip->rhythm_addr[channel] & 1) { data = chip->rhythm_data[channel] & 0x0f; } else { chip->rhythm_data[channel] = YM2608_ADPCM_ROM[chip->rhythm_addr[channel] >> 1]; data = (chip->rhythm_data[channel] >> 4) & 0x0f; } chip->rhythm_addr[channel]++; chip->rhythm_adpcm_acc[channel] += adpcm_jedi_table[chip->rhythm_adpcm_step[channel] + data]; /* the 12-bit accumulator wraps on the ym2610 and ym2608 (like the msm5205), it does not saturate (like the msm5218) */ chip->rhythm_adpcm_acc[channel] &= 0xfff; /* extend 12-bit signed int */ if (chip->rhythm_adpcm_acc[channel] & 0x800) { chip->rhythm_adpcm_acc[channel] |= ~0xfff; } chip->rhythm_adpcm_step[channel] += adpcm_step_inc[data & 7]; if (chip->rhythm_adpcm_step[channel] > 48 * 16) { chip->rhythm_adpcm_step[channel] = 48 * 16; } if (chip->rhythm_adpcm_step[channel] < 0) { chip->rhythm_adpcm_step[channel] = 0; } } while (--step); /* calc pcm * volume data */ out = ((chip->rhythm_adpcm_acc[channel] * chip->rhythm_vol_mul[channel]) >> chip->rhythm_vol_shift[channel]) & ~3; /* multiply, shift and mask out 2 LSB bits */ end: chip->rhythml[channel] = panl ? (out >> 1) : 0; chip->rhythmr[channel] = panr ? (out >> 1) : 0; } } } /*OPN-MOD: generate ADPCM DeltaT*/ void OPNmod_DeltaTGenerate(ym3438_t *chip) { Bit32u channel = chip->channel; Bit32s *out = chip->out_deltaT; out[0] = 0; out[1] = 0; out[2] = 0; out[3] = 0; if (chip->cycles == 0) { if (chip->deltaT.portstate & 0x80 /* && !chip->mute_deltaT */) YM_DELTAT_ADPCM_CALC(&chip->deltaT); chip->deltaTl[channel] = (out[2/*LEFT*/] + out[3/*CENTER*/]) / (1 << 8); chip->deltaTr[channel] = (out[1/*RIGHT*/] + out[3/*CENTER*/]) / (1 << 8); } else { chip->deltaTl[channel] = 0; chip->deltaTr[channel] = 0; } } void OPN2_DoTimerA(ym3438_t *chip) { Bit16u time; Bit8u load; load = chip->timer_a_overflow; if (chip->cycles == 2) { /* Lock load value */ load |= (!chip->timer_a_load_lock && chip->timer_a_load); chip->timer_a_load_lock = chip->timer_a_load; if (chip->mode_csm) { /* CSM KeyOn */ chip->mode_kon_csm = load; } else { chip->mode_kon_csm = 0; } } /* Load counter */ if (chip->timer_a_load_latch) { time = chip->timer_a_reg; } else { time = chip->timer_a_cnt; } chip->timer_a_load_latch = load; /* Increase counter */ if ((chip->cycles == 1 && chip->timer_a_load_lock) || chip->mode_test_21[2]) { time++; } /* Set overflow flag */ if (chip->timer_a_reset) { chip->timer_a_reset = 0; chip->timer_a_overflow_flag = 0; } else { chip->timer_a_overflow_flag |= chip->timer_a_overflow & chip->timer_a_enable; } chip->timer_a_overflow = (time >> 10); chip->timer_a_cnt = time & 0x3ff; } void OPN2_DoTimerB(ym3438_t *chip) { Bit16u time; Bit8u load; load = chip->timer_b_overflow; if (chip->cycles == 2) { /* Lock load value */ load |= (!chip->timer_b_load_lock && chip->timer_b_load); chip->timer_b_load_lock = chip->timer_b_load; } /* Load counter */ if (chip->timer_b_load_latch) { time = chip->timer_b_reg; } else { time = chip->timer_b_cnt; } chip->timer_b_load_latch = load; /* Increase counter */ if (chip->cycles == 1) { chip->timer_b_subcnt++; } if ((chip->timer_b_subcnt == 0x10 && chip->timer_b_load_lock) || chip->mode_test_21[2]) { time++; } chip->timer_b_subcnt &= 0x0f; /* Set overflow flag */ if (chip->timer_b_reset) { chip->timer_b_reset = 0; chip->timer_b_overflow_flag = 0; } else { chip->timer_b_overflow_flag |= chip->timer_b_overflow & chip->timer_b_enable; } chip->timer_b_overflow = (time >> 8); chip->timer_b_cnt = time & 0xff; } void OPN2_KeyOn(ym3438_t*chip) { Bit32u slot = chip->cycles; Bit32u chan = chip->channel; /* Key On */ chip->eg_kon_latch[slot] = chip->mode_kon[slot]; chip->eg_kon_csm[slot] = 0; if (chip->channel == 2 && chip->mode_kon_csm) { /* CSM Key On */ chip->eg_kon_latch[slot] = 1; chip->eg_kon_csm[slot] = 1; } if (chip->cycles == chip->mode_kon_channel) { /* OP1 */ chip->mode_kon[chan] = chip->mode_kon_operator[0]; /* OP2 */ chip->mode_kon[chan + 12] = chip->mode_kon_operator[1]; /* OP3 */ chip->mode_kon[chan + 6] = chip->mode_kon_operator[2]; /* OP4 */ chip->mode_kon[chan + 18] = chip->mode_kon_operator[3]; } } static void OPNmod_deltat_status_set(void *userdata, Bit8u changebits) { ym3438_t *chip = (ym3438_t *)userdata; chip->status |= ~changebits; } static void OPNmod_deltat_status_reset(void *userdata, Bit8u changebits) { ym3438_t *chip = (ym3438_t *)userdata; chip->status &= ~changebits; } void OPN2_Reset(ym3438_t *chip, Bit32u clock, const struct OPN2mod_psg_callbacks *psg, void *psgdata, Bit32u dramsize) { Bit32u i; Bit8u *pcmmem = chip->deltaT.memory; Bit32u pcmoldsize = chip->deltaT.memory_size; memset(chip, 0, sizeof(ym3438_t)); chip->deltaT.memory = pcmmem; for (i = 0; i < 24; i++) { chip->eg_out[i] = 0x3ff; chip->eg_level[i] = 0x3ff; chip->eg_state[i] = eg_num_release; chip->multi[i] = 1; } for (i = 0; i < 6; i++) { chip->pan_l[i] = 1; chip->pan_r[i] = 1; } /*OPN-MOD: initialize and connect PSG*/ chip->psg = psg; chip->psgdata = psgdata; psg->Reset(psgdata); psg->SetClock(psgdata, clock / 4); /*OPN-MOD: initialize rhythm*/ for (i = 0; i < 6; i++) { chip->rhythm_pan[i] = 3; chip->rhythm_step[i] = (Bit32u)((1 << adpcm_shift) / ((i < 4) ? 3.0 : 6.0)); OPNmod_RhythmUpdateVolume(chip, i); } /*OPN-MOD: initialize ADPCM*/ /*//chip->deltaT.memory = (UINT8 *)pcmrom; //chip->deltaT.memory_size = pcmsize; //chip->deltaT.memory = NULL; //chip->deltaT.memory_size = 0x00; //chip->deltaT.memory_mask = 0x00;*/ chip->deltaT.memory = pcmmem = (Bit8u *)realloc(pcmmem, dramsize); chip->deltaT.memory_size = dramsize; if (!pcmmem) abort(); if (pcmoldsize < dramsize) memset(pcmmem + pcmoldsize, 0, dramsize - pcmoldsize); YM_DELTAT_calc_mem_mask(&chip->deltaT); /*chip->deltaT.write_time = 20.0 / clock;*/ /* a single byte write takes 20 cycles of main clock */ /*chip->deltaT.read_time = 18.0 / clock;*/ /* a single byte read takes 18 cycles of main clock */ chip->deltaT.status_set_handler = &OPNmod_deltat_status_set; chip->deltaT.status_reset_handler = &OPNmod_deltat_status_reset; chip->deltaT.status_change_which_chip = chip; chip->deltaT.status_change_EOS_bit = 0x04; /* status flag: set bit2 on End Of Sample */ chip->deltaT.status_change_BRDY_bit = 0x08; /* status flag: set bit3 on BRDY */ chip->deltaT.status_change_ZERO_bit = 0x10; /* status flag: set bit4 if silence continues for more than 290 miliseconds while recording the ADPCM */ YM_DELTAT_ADPCM_Init(&chip->deltaT,YM_DELTAT_EMULATION_MODE_NORMAL,5,chip->out_deltaT,1<<23); chip->deltaT.freqbase = 1.0; /* TODO(jpc) set the freqbase value to fixed */ YM_DELTAT_ADPCM_Reset(&chip->deltaT, 3/*CENTER*/); } void OPN2_Destroy(ym3438_t *chip) { if (!chip) return; free(chip->deltaT.memory); chip->deltaT.memory = NULL; } void OPN2_SetChipType(Bit32u type) { chip_type = type; } void OPN2_Clock(ym3438_t *chip, Bit16s *buffer) { Bit32u slot = chip->cycles; Bit16s *rhythml = &chip->rhythml[chip->channel]; Bit16s *rhythmr = &chip->rhythmr[chip->channel]; Bit16s *deltaTl = &chip->deltaTl[chip->channel]; Bit16s *deltaTr = &chip->deltaTr[chip->channel]; chip->lfo_inc = chip->mode_test_21[1]; chip->pg_read >>= 1; chip->eg_read[1] >>= 1; chip->eg_cycle++; /* Lock envelope generator timer value */ if (chip->cycles == 1 && chip->eg_quotient == 2) { if (chip->eg_cycle_stop) { chip->eg_shift_lock = 0; } else { chip->eg_shift_lock = chip->eg_shift + 1; } chip->eg_timer_low_lock = chip->eg_timer & 0x03; } /* Cycle specific functions */ switch (chip->cycles) { case 0: chip->lfo_pm = chip->lfo_cnt >> 2; if (chip->lfo_cnt & 0x40) { chip->lfo_am = chip->lfo_cnt & 0x3f; } else { chip->lfo_am = chip->lfo_cnt ^ 0x3f; } chip->lfo_am <<= 1; break; case 1: chip->eg_quotient++; chip->eg_quotient %= 3; chip->eg_cycle = 0; chip->eg_cycle_stop = 1; chip->eg_shift = 0; chip->eg_timer_inc |= chip->eg_quotient >> 1; chip->eg_timer = chip->eg_timer + chip->eg_timer_inc; chip->eg_timer_inc = chip->eg_timer >> 12; chip->eg_timer &= 0xfff; break; case 2: chip->pg_read = chip->pg_phase[21] & 0x3ff; chip->eg_read[1] = chip->eg_out[0]; break; case 13: chip->eg_cycle = 0; chip->eg_cycle_stop = 1; chip->eg_shift = 0; chip->eg_timer = chip->eg_timer + chip->eg_timer_inc; chip->eg_timer_inc = chip->eg_timer >> 12; chip->eg_timer &= 0xfff; break; case 23: chip->lfo_inc |= 1; break; } chip->eg_timer &= ~(chip->mode_test_21[5] << chip->eg_cycle); if (((chip->eg_timer >> chip->eg_cycle) | (chip->pin_test_in & chip->eg_custom_timer)) & chip->eg_cycle_stop) { chip->eg_shift = chip->eg_cycle; chip->eg_cycle_stop = 0; } OPN2_DoIO(chip); OPN2_DoTimerA(chip); OPN2_DoTimerB(chip); OPN2_KeyOn(chip); OPN2_ChOutput(chip); OPN2_ChGenerate(chip); OPN2_FMPrepare(chip); OPN2_FMGenerate(chip); OPNmod_RhythmGenerate(chip); OPNmod_DeltaTGenerate(chip); OPN2_PhaseGenerate(chip); OPN2_PhaseCalcIncrement(chip); OPN2_EnvelopeADSR(chip); OPN2_EnvelopeGenerate(chip); OPN2_EnvelopeSSGEG(chip); OPN2_EnvelopePrepare(chip); /* Prepare fnum & block */ if (chip->mode_ch3) { /* Channel 3 special mode */ switch (slot) { case 1: /* OP1 */ chip->pg_fnum = chip->fnum_3ch[1]; chip->pg_block = chip->block_3ch[1]; chip->pg_kcode = chip->kcode_3ch[1]; break; case 7: /* OP3 */ chip->pg_fnum = chip->fnum_3ch[0]; chip->pg_block = chip->block_3ch[0]; chip->pg_kcode = chip->kcode_3ch[0]; break; case 13: /* OP2 */ chip->pg_fnum = chip->fnum_3ch[2]; chip->pg_block = chip->block_3ch[2]; chip->pg_kcode = chip->kcode_3ch[2]; break; case 19: /* OP4 */ default: chip->pg_fnum = chip->fnum[(chip->channel + 1) % 6]; chip->pg_block = chip->block[(chip->channel + 1) % 6]; chip->pg_kcode = chip->kcode[(chip->channel + 1) % 6]; break; } } else { chip->pg_fnum = chip->fnum[(chip->channel + 1) % 6]; chip->pg_block = chip->block[(chip->channel + 1) % 6]; chip->pg_kcode = chip->kcode[(chip->channel + 1) % 6]; } OPN2_UpdateLFO(chip); OPN2_DoRegWrite(chip); chip->cycles = (chip->cycles + 1) % 24; chip->channel = chip->cycles % 6; buffer[0] = chip->mol; buffer[1] = chip->mor; /*OPN-MOD: Rhythm output*/ buffer[2] = *rhythml; buffer[3] = *rhythmr; /*OPN-MOD: DeltaT output*/ buffer[4] = *deltaTl; buffer[5] = *deltaTr; if (chip->status_time) chip->status_time--; } void OPN2_Write(ym3438_t *chip, Bit32u port, Bit8u data) { port &= 3; chip->write_data = ((port << 7) & 0x100) | data; if (port & 1) { /* Data */ chip->write_d |= 1; } else { /* Address */ chip->write_a |= 1; } } void OPN2_SetTestPin(ym3438_t *chip, Bit32u value) { chip->pin_test_in = value & 1; } Bit32u OPN2_ReadTestPin(ym3438_t *chip) { if (!chip->mode_test_2c[7]) { return 0; } return chip->cycles == 23; } Bit32u OPN2_ReadIRQPin(ym3438_t *chip) { return chip->timer_a_overflow_flag | chip->timer_b_overflow_flag; } Bit8u OPN2_Read(ym3438_t *chip, Bit32u port) { Bit32u slot; Bit16u testdata; if ((port & 3) == 0 || (chip_type & ym3438_mode_readmode)) { if (chip->mode_test_21[6]) { /* Read test data */ /*Bit32u slot = (chip->cycles + 18) % 24; Bit16u testdata = ((chip->pg_read & 0x01) << 15) | ((chip->eg_read[chip->mode_test_21[0]] & 0x01) << 14);*/ slot = (chip->cycles + 18) % 24; testdata = ((chip->pg_read & 0x01) << 15) | ((chip->eg_read[chip->mode_test_21[0]] & 0x01) << 14); if (chip->mode_test_2c[4]) { testdata |= chip->ch_read & 0x1ff; } else { testdata |= chip->fm_out[slot] & 0x3fff; } if (chip->mode_test_21[7]) { chip->status = testdata & 0xff; } else { chip->status = testdata >> 8; } } else { chip->status = (chip->busy << 7) | (chip->timer_b_overflow_flag << 1) | chip->timer_a_overflow_flag; } /*OPN-MOD: status of DeltaT */ chip->status |= (chip->deltaT.PCM_BSY & 1) << 5; if (chip_type & ym3438_mode_ym2612) { chip->status_time = 300000; } else { chip->status_time = 40000000; } } /*OPN-MOD: read from PSG*/ if ((port & 3) == 1) { if (chip->address < 16) { return chip->psg->Read(chip->psgdata); } else if(chip->address == 0xff) { return 1; /* ID code */ } } /*OPN-MOD: read from DeltaT*/ if ((port & 3) == 3) { if (chip->address == 0x08) { return YM_DELTAT_ADPCM_Read(&chip->deltaT); } } if (chip->status_time) { return chip->status; } return 0; } void OPN2_WriteBuffered(ym3438_t *chip, Bit32u port, Bit8u data) { Bit64u time1, time2; Bit16s buffer[6]; Bit64u skip; if (chip->writebuf[chip->writebuf_last].port & 0x04) { OPN2_Write(chip, chip->writebuf[chip->writebuf_last].port & 0X03, chip->writebuf[chip->writebuf_last].data); chip->writebuf_cur = (chip->writebuf_last + 1) % OPN_WRITEBUF_SIZE; skip = chip->writebuf[chip->writebuf_last].time - chip->writebuf_samplecnt; chip->writebuf_samplecnt = chip->writebuf[chip->writebuf_last].time; while (skip--) { OPN2_Clock(chip, buffer); } } chip->writebuf[chip->writebuf_last].port = (port & 0x03) | 0x04; chip->writebuf[chip->writebuf_last].data = data; time1 = chip->writebuf_lasttime + OPN_WRITEBUF_DELAY; time2 = chip->writebuf_samplecnt; if (time1 < time2) { time1 = time2; } chip->writebuf[chip->writebuf_last].time = time1; chip->writebuf_lasttime = time1; chip->writebuf_last = (chip->writebuf_last + 1) % OPN_WRITEBUF_SIZE; } void OPN2_FlushBuffer(ym3438_t *chip) { Bit16s buffer[6]; Bit64u skip; while (chip->writebuf[chip->writebuf_cur].port & 0x04) { chip->writebuf[chip->writebuf_cur].port &= 0x03; OPN2_Write(chip, chip->writebuf[chip->writebuf_cur].port, chip->writebuf[chip->writebuf_cur].data); skip = chip->writebuf[chip->writebuf_cur].time - chip->writebuf_samplecnt; chip->writebuf_samplecnt = chip->writebuf[chip->writebuf_cur].time; chip->writebuf_cur = (chip->writebuf_cur + 1) % OPN_WRITEBUF_SIZE; while (skip--) { OPN2_Clock(chip, buffer); } } } void OPN2_Generate(ym3438_t *chip, sample *samples) { Bit32u i; Bit16s buffer[6]; samples[0] = 0; samples[1] = 0; for (i = 0; i < 24; i++) { OPN2_Clock(chip, buffer); /*OPN-MOD: adjust FM output to mix*/ samples[0] += (buffer[0] * 11) >> 4; samples[1] += (buffer[1] * 11) >> 4; /*OPN-MOD: mix rhythm samples*/ samples[0] += buffer[2]; samples[1] += buffer[3]; /*OPN-MOD: mix deltaT samples*/ samples[0] += buffer[4]; samples[1] += buffer[5]; while (chip->writebuf[chip->writebuf_cur].time <= chip->writebuf_samplecnt) { if (!(chip->writebuf[chip->writebuf_cur].port & 0x04)) { break; } chip->writebuf[chip->writebuf_cur].port &= 0x03; OPN2_Write(chip, chip->writebuf[chip->writebuf_cur].port, chip->writebuf[chip->writebuf_cur].data); chip->writebuf_cur = (chip->writebuf_cur + 1) % OPN_WRITEBUF_SIZE; } chip->writebuf_samplecnt++; } } BambooTracker-0.6.5/BambooTracker/chip/nuked/ym3438.h000066400000000000000000000160671476276175200221540ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Alexey Khokholov (Nuke.YKT) * Copyright (C) 2019 Jean Pierre Cimalando * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * * Nuked OPN2-MOD emulator, with OPNA functionality added. * Thanks: * Silicon Pr0n: * Yamaha YM3438 decap and die shot(digshadow). * OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): * OPL2 ROMs. * * version: 1.0.9 * * OPN-MOD additions: * - Jean Pierre Cimalando 2019-04-06: add SSG control interface * - Jean Pierre Cimalando 2019-04-06: add 6-channel FM flag * - Jean Pierre Cimalando 2019-04-06: add ADPCM rhythm channels * - Jean Pierre Cimalando 2019-04-07: raise the channel clipping threshold */ #ifndef YM3438_H #define YM3438_H #include "../chip_defs.h" #include "../mame/ymdeltat.h" #ifdef __cplusplus extern "C" { #endif /*EXTRA*/ #define RSM_FRAC 10 #define OPN_WRITEBUF_SIZE 2048 #define OPN_WRITEBUF_DELAY 15 enum { ym3438_mode_ym2612 = 0x01, /* Enables YM2612 emulation (MD1, MD2 VA2) */ ym3438_mode_readmode = 0x02 /* Enables status read on any port (TeraDrive, MD1 VA7, MD2, etc) */ }; #include typedef uintptr_t Bitu; typedef intptr_t Bits; typedef uint64_t Bit64u; typedef int64_t Bit64s; typedef uint32_t Bit32u; typedef int32_t Bit32s; typedef uint16_t Bit16u; typedef int16_t Bit16s; typedef uint8_t Bit8u; typedef int8_t Bit8s; /*EXTRA*/ typedef struct _opn2_writebuf { Bit64u time; Bit8u port; Bit8u data; Bit8u reserved[6]; } opn2_writebuf; typedef struct { Bit32u cycles; Bit32u channel; Bit16s mol, mor; /*OPN-MOD: Rhythm channel outputs*/ Bit16s rhythml[6]; Bit16s rhythmr[6]; /*OPN-MOD: DeltaT channel outputs*/ Bit16s deltaTl[6]; Bit16s deltaTr[6]; /* IO */ Bit16u write_data; Bit8u write_a; Bit8u write_d; Bit8u write_a_en; Bit8u write_d_en; Bit8u write_busy; Bit8u write_busy_cnt; Bit8u write_fm_address; Bit8u write_fm_data; Bit8u write_fm_mode_a; Bit16u address; Bit8u data; Bit8u pin_test_in; Bit8u pin_irq; Bit8u busy; /* LFO */ Bit8u lfo_en; Bit8u lfo_freq; Bit8u lfo_pm; Bit8u lfo_am; Bit8u lfo_cnt; Bit8u lfo_inc; Bit8u lfo_quotient; /* Phase generator */ Bit16u pg_fnum; Bit8u pg_block; Bit8u pg_kcode; Bit32u pg_inc[24]; Bit32u pg_phase[24]; Bit8u pg_reset[24]; Bit32u pg_read; /* Envelope generator */ Bit8u eg_cycle; Bit8u eg_cycle_stop; Bit8u eg_shift; Bit8u eg_shift_lock; Bit8u eg_timer_low_lock; Bit16u eg_timer; Bit8u eg_timer_inc; Bit16u eg_quotient; Bit8u eg_custom_timer; Bit8u eg_rate; Bit8u eg_ksv; Bit8u eg_inc; Bit8u eg_ratemax; Bit8u eg_sl[2]; Bit8u eg_lfo_am; Bit8u eg_tl[2]; Bit8u eg_state[24]; Bit16u eg_level[24]; Bit16u eg_out[24]; Bit8u eg_kon[24]; Bit8u eg_kon_csm[24]; Bit8u eg_kon_latch[24]; Bit8u eg_csm_mode[24]; Bit8u eg_ssg_enable[24]; Bit8u eg_ssg_pgrst_latch[24]; Bit8u eg_ssg_repeat_latch[24]; Bit8u eg_ssg_hold_up_latch[24]; Bit8u eg_ssg_dir[24]; Bit8u eg_ssg_inv[24]; Bit32u eg_read[2]; Bit8u eg_read_inc; /* FM */ Bit16s fm_op1[6][2]; Bit16s fm_op2[6]; Bit16s fm_out[24]; Bit16u fm_mod[24]; /* Channel */ Bit16s ch_acc[6]; Bit16s ch_out[6]; Bit16s ch_lock; Bit8u ch_lock_l; Bit8u ch_lock_r; Bit16s ch_read; /*OPNA: Rhythm channel*/ Bit8u rhythm_tl; Bit8u rhythm_key[6]; Bit8u rhythm_pan[6]; Bit8u rhythm_level[6]; Bit16u rhythm_addr[6]; Bit8u rhythm_data[6]; Bit32u rhythm_step[6]; Bit32u rhythm_now_step[6]; Bit32s rhythm_adpcm_step[6]; Bit32s rhythm_adpcm_acc[6]; Bit8s rhythm_vol_mul[6]; Bit8u rhythm_vol_shift[6]; /* Timer */ Bit16u timer_a_cnt; Bit16u timer_a_reg; Bit8u timer_a_load_lock; Bit8u timer_a_load; Bit8u timer_a_enable; Bit8u timer_a_reset; Bit8u timer_a_load_latch; Bit8u timer_a_overflow_flag; Bit8u timer_a_overflow; Bit16u timer_b_cnt; Bit8u timer_b_subcnt; Bit16u timer_b_reg; Bit8u timer_b_load_lock; Bit8u timer_b_load; Bit8u timer_b_enable; Bit8u timer_b_reset; Bit8u timer_b_load_latch; Bit8u timer_b_overflow_flag; Bit8u timer_b_overflow; /* Register set */ Bit8u mode_test_21[8]; Bit8u mode_test_2c[8]; Bit8u mode_ch3; Bit8u mode_kon_channel; Bit8u mode_kon_operator[4]; Bit8u mode_kon[24]; Bit8u mode_csm; Bit8u mode_kon_csm; Bit8u dacen; Bit16s dacdata; /*OPN-MOD: 6 FM channel flag*/ Bit8u mode_fm6ch; Bit8u ks[24]; Bit8u ar[24]; Bit8u sr[24]; Bit8u dt[24]; Bit8u multi[24]; Bit8u sl[24]; Bit8u rr[24]; Bit8u dr[24]; Bit8u am[24]; Bit8u tl[24]; Bit8u ssg_eg[24]; Bit16u fnum[6]; Bit8u block[6]; Bit8u kcode[6]; Bit16u fnum_3ch[6]; Bit8u block_3ch[6]; Bit8u kcode_3ch[6]; Bit8u reg_a4; Bit8u reg_ac; Bit8u connect[6]; Bit8u fb[6]; Bit8u pan_l[6], pan_r[6]; Bit8u ams[6]; Bit8u pms[6]; Bit8u status; Bit32u status_time; /*EXTRA*/ Bit64u writebuf_samplecnt; Bit32u writebuf_cur; Bit32u writebuf_last; Bit64u writebuf_lasttime; opn2_writebuf writebuf[OPN_WRITEBUF_SIZE]; /*OPN-MOD*/ const struct OPN2mod_psg_callbacks *psg; void *psgdata; /*OPN-MOD*/ YM_DELTAT deltaT; Bit32s out_deltaT[4]; } ym3438_t; void OPN2_Reset(ym3438_t *chip, Bit32u clock, const struct OPN2mod_psg_callbacks *psg, void *psgdata, Bit32u dramsize); void OPN2_Destroy(ym3438_t *chip); void OPN2_SetChipType(Bit32u type); void OPN2_Clock(ym3438_t *chip, Bit16s *buffer); void OPN2_Write(ym3438_t *chip, Bit32u port, Bit8u data); void OPN2_SetTestPin(ym3438_t *chip, Bit32u value); Bit32u OPN2_ReadTestPin(ym3438_t *chip); Bit32u OPN2_ReadIRQPin(ym3438_t *chip); Bit8u OPN2_Read(ym3438_t *chip, Bit32u port); /*EXTRA*/ void OPN2_WriteBuffered(ym3438_t *chip, Bit32u port, Bit8u data); void OPN2_FlushBuffer(ym3438_t *chip); void OPN2_Generate(ym3438_t *chip, sample *samples); /*OPN-MOD*/ struct OPN2mod_psg_callbacks { void (*SetClock)(void *param, int clock); void (*Write)(void *param, int address, int data); int (*Read)(void *param); void (*Reset)(void *param); }; #ifdef __cplusplus } #endif #endif BambooTracker-0.6.5/BambooTracker/chip/opna.cpp000066400000000000000000000270241476276175200213620ustar00rootroot00000000000000/* * Copyright (C) 2018-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "opna.hpp" #include #include #include #include "register_write_logger.hpp" #include "mame/mame_2608.hpp" #include "nuked/nuked_2608.hpp" #include "ymfm/ymfm_2608.hpp" #ifdef USE_REAL_CHIP #include "scci/scci_wrapper.hpp" #include "c86ctl/c86ctl_wrapper.hpp" #endif namespace chip { namespace { constexpr double VOL_REDUC_ = 7.5; // 55466Hz: FM internal rate constexpr int DEFAULT_AUTO_RATE = 55466; enum SoundSourceIndex : int { FM = 0, SSG = 1 }; enum WriteMode : int { WAIT_MODE = 0, IMMEDIATE_MODE = 1 }; inline double clamp(double value, double low, double high) { return std::min(std::max(value, low), high); } void gainSamples(sample** samples, size_t nSamples, double gain) { for (int pan = STEREO_LEFT; pan <= STEREO_RIGHT; ++pan) { std::transform(samples[pan], samples[pan] + nSamples, samples[pan], [gain](sample s) { return static_cast(s * gain); }); } } } size_t OPNA::count_ = 0; OPNA::OPNA(OpnaEmulator emu, int clock, int rate, size_t maxDuration, size_t dramSize, std::unique_ptr fmResampler, std::unique_ptr ssgResampler, std::shared_ptr logger) : Chip(count_++, clock, rate, DEFAULT_AUTO_RATE, maxDuration, std::move(fmResampler), std::move(ssgResampler), logger), volumeFm_(0), volumeSsg_(0), dramSize_(dramSize), rcIntf_(std::make_unique()), isForcedRegWrite_(false), waitRestFm_(0), waitRestSsg2_(0), writeFuncs { // Wait mode { &OPNA::enqueueData, &OPNA::storeBufferForWait }, // Immediate mode { &OPNA::writeDataImmediately, &OPNA::storeBufferForImmediate } }, writeFunc(&writeFuncs[WAIT_MODE]) { for (int pan = STEREO_LEFT; pan <= STEREO_RIGHT; ++pan) { tmpBuf_[pan] = new sample[CHIP_SMPL_BUF_SIZE_]; } switch (emu) { default: fprintf(stderr, "Unknown emulator choice. Using the default.\n"); /* fall through */ case OpnaEmulator::Mame: fprintf(stderr, "Using emulator: MAME YM2608\n"); intf_ = std::make_unique(); break; case OpnaEmulator::Nuked: fprintf(stderr, "Using emulator: Nuked OPN-Mod\n"); intf_ = std::make_unique(); break; case OpnaEmulator::Ymfm: fprintf(stderr, "Using emulator: ymfm\n"); intf_ = std::make_unique(); break; } funcSetRate(rate); internalRate_[FM] = intf_->startDevice(clock, internalRate_[SSG], dramSize); rate2_ = static_cast((internalRate_[SSG] << 1) / internalRate_[FM]); // Should be "9" initResampler(); setVolumeFM(0); setVolumeSSG(0); reset(); } OPNA::~OPNA() { intf_->stopDevice(); --count_; for (int pan = STEREO_LEFT; pan <= STEREO_RIGHT; ++pan) { delete[] tmpBuf_[pan]; } } void OPNA::resetSpecific() { isForcedRegWrite_ = false; regWrites_.clear(); forcedRegWrites_.clear(); waitRestFm_ = 0; waitRestSsg2_ = 0; intf_->resetDevice(); rcIntf_->reset(); } void OPNA::setImmediateWriteMode(bool enabled) noexcept { writeFunc = &writeFuncs[enabled ? IMMEDIATE_MODE : WAIT_MODE]; } bool OPNA::isImmediateWriteMode() const noexcept { return writeFunc == &writeFuncs[IMMEDIATE_MODE]; } void OPNA::setRegister(uint32_t offset, uint8_t value) { std::lock_guard lg(mutex_); if (logger_) { logger_->recordRegisterChange(offset, value); } else { (this->*writeFunc->setRegister)(offset, value); } rcIntf_->setRegister(offset, value); } void OPNA::enqueueData(uint32_t offset, uint8_t value) { if (isForcedRegWrite_) { forcedRegWrites_.push_back({ offset & 0x0ff, value, !(offset & 0x100) }); } else { regWrites_.push_back({ offset & 0x0ff, value, !(offset & 0x100) }); } } void OPNA::writeDataImmediately(uint32_t offset, uint8_t value) { if (offset & 0x100) { intf_->writeAddressToPortB(offset & 0xff); intf_->writeDataToPortB(value & 0xff); } else { intf_->writeAddressToPortA(offset & 0xff); intf_->writeDataToPortA(value & 0xff); } } uint8_t OPNA::getRegister(uint32_t offset) const { if (offset & 0x100) intf_->writeAddressToPortB(offset & 0xff); else intf_->writeAddressToPortA(offset & 0xff); return intf_->readData(); } void OPNA::setVolumeFM(double dB) { std::lock_guard lg(mutex_); volumeFm_ = dB; busVolumeRatio_[FM] = std::pow(10.0, (dB - VOL_REDUC_) / 20.0) / VOLUME_RATIO_MOD_; updateVolumeRatio(FM); } void OPNA::setVolumeSSG(double dB) { std::lock_guard lg(mutex_); volumeSsg_ = dB; busVolumeRatio_[SSG] = std::pow(10.0, (dB - VOL_REDUC_) / 20.0) / VOLUME_RATIO_MOD_; updateVolumeRatio(SSG); rcIntf_->setSSGVolume(dB); } size_t OPNA::getDRAMSize() const noexcept { return dramSize_; } bool OPNA::mix(int16_t* stream, size_t nSamples) { std::lock_guard lg(mutex_); size_t pointFm = 0; size_t pointSsg = 0; // Store samples to internal buffer bool result = (this->*writeFunc->storeBuffer)(nSamples, pointFm, pointSsg); if (!result) return false; // Gain volume gainSamples(buffer_[FM], pointFm, volumeRatio_[FM]); gainSamples(buffer_[SSG], pointSsg, volumeRatio_[SSG]); // Resampling sample** bufFM = resampler_[FM]->interpolate(buffer_[FM], nSamples, pointFm); sample** bufSSG = resampler_[SSG]->interpolate(buffer_[SSG], nSamples, pointSsg); // Mix int16_t* p = stream; for (size_t i = 0; i < nSamples; ++i) { for (int pan = STEREO_LEFT; pan <= STEREO_RIGHT; ++pan) { *p++ = static_cast(clamp((bufFM[pan][i] + bufSSG[pan][i]) * VOLUME_RATIO_MOD_, -32768, 32767)); } } return true; } /** * @brief OPNA::dequeueData * @return wait count as FM internal sample rate */ size_t OPNA::dequeueData() { size_t waitCount = 0; if (!forcedRegWrites_.empty()) { auto& unit = forcedRegWrites_.front(); if (unit.isPortA_) { intf_->writeAddressToPortA(unit.address & 0xff); intf_->writeDataToPortA(unit.data & 0xff); } else { intf_->writeAddressToPortB(unit.address & 0xff); intf_->writeDataToPortB(unit.data & 0xff); } waitCount = unit.address == 0x10 ? 4 : 1; forcedRegWrites_.pop_front(); } else if (!regWrites_.empty()) { auto& unit = regWrites_.front(); if (unit.isPortA_) { intf_->writeAddressToPortA(unit.address & 0xff); intf_->writeDataToPortA(unit.data & 0xff); } else { intf_->writeAddressToPortB(unit.address & 0xff); intf_->writeDataToPortB(unit.data & 0xff); } waitCount = unit.address == 0x10 ? 4 : 1; regWrites_.pop_front(); } return waitCount; } bool OPNA::storeBufferForImmediate(size_t nSamples, size_t& pointFm, size_t& pointSsg) { bool ok = false; size_t intrSizeFm = resampler_[FM]->calculateInternalSampleSize(nSamples, ok); if (!ok) return false; intf_->updateStream(tmpBuf_, intrSizeFm); for (int pan = STEREO_LEFT; pan <= STEREO_RIGHT; ++pan) { std::copy_n(tmpBuf_[pan], intrSizeFm, buffer_[FM][pan] + pointFm); } pointFm += intrSizeFm; size_t intrSizeSsg = resampler_[SSG]->calculateInternalSampleSize(nSamples, ok); if (!ok) return false; intf_->updateSsgStream(tmpBuf_, intrSizeSsg); for (int pan = STEREO_LEFT; pan <= STEREO_RIGHT; ++pan) { std::copy_n(tmpBuf_[pan], intrSizeSsg, buffer_[SSG][pan] + pointSsg); } pointSsg += intrSizeSsg; return true; } void OPNA::flushWait(size_t& pointFm, size_t maxFm, size_t& pointSsg2, size_t maxSsg2) { size_t sizeFm = std::min(waitRestFm_, maxFm - pointFm); intf_->updateStream(tmpBuf_, sizeFm); for (int pan = STEREO_LEFT; pan <= STEREO_RIGHT; ++pan) { std::copy_n(tmpBuf_[pan], sizeFm, buffer_[FM][pan] + pointFm); } waitRestFm_ -= sizeFm; pointFm += sizeFm; size_t pointSsg = pointSsg2 >> 1; size_t endPointSsg2 = std::min(pointSsg2 + waitRestSsg2_, maxSsg2); size_t sizeSsg = (endPointSsg2 >> 1) - pointSsg; intf_->updateSsgStream(tmpBuf_, sizeSsg); for (int pan = STEREO_LEFT; pan <= STEREO_RIGHT; ++pan) { std::copy_n(tmpBuf_[pan], sizeSsg, buffer_[SSG][pan] + pointSsg); } size_t sizeSsg2 = endPointSsg2 - pointSsg2; waitRestSsg2_ -= sizeSsg2; pointSsg2 += sizeSsg2; } bool OPNA::storeBufferForWait(size_t nSamples, size_t& pointFm, size_t& pointSsg) { bool ok = false; size_t intrSizeFm = resampler_[FM]->calculateInternalSampleSize(nSamples, ok); if (!ok) return false; size_t intrSizeSsg2 = resampler_[SSG]->calculateInternalSampleSize(nSamples, ok) << 1; if (!ok) return false; size_t pointSsg2 = pointSsg << 1; // Flush previous wait flushWait(pointFm, intrSizeFm, pointSsg2, intrSizeSsg2); // Wait and generate while (pointFm < intrSizeFm || pointSsg2 < intrSizeSsg2) { size_t waitCount = dequeueData(); if (!waitCount) break; // Add wait count waitRestFm_ += waitCount; waitRestSsg2_ += rate2_ * waitCount; flushWait(pointFm, intrSizeFm, pointSsg2, intrSizeSsg2); } // Generate rest samples size_t sizeFm = intrSizeFm - pointFm; intf_->updateStream(tmpBuf_, sizeFm); for (int pan = STEREO_LEFT; pan <= STEREO_RIGHT; ++pan) { std::copy_n(tmpBuf_[pan], sizeFm, buffer_[FM][pan] + pointFm); } if (sizeFm <= waitRestFm_) waitRestFm_ -= sizeFm; pointFm += sizeFm; pointSsg = pointSsg2 >> 1; size_t sizeSsg = (intrSizeSsg2 >> 1) - pointSsg; intf_->updateSsgStream(tmpBuf_, sizeSsg); for (int pan = STEREO_LEFT; pan <= STEREO_RIGHT; ++pan) { std::copy_n(tmpBuf_[pan], sizeSsg, buffer_[SSG][pan] + pointSsg); } size_t sizeSsg2 = intrSizeSsg2 - pointSsg2; if (sizeSsg2 <= waitRestSsg2_) waitRestSsg2_ -= sizeSsg2; pointSsg = (pointSsg2 + sizeSsg2) >> 1; return true; } void OPNA::setFmResampler(std::unique_ptr resampler) { std::lock_guard lg(mutex_); resampler_[FM] = std::move(resampler); initResampler(); } void OPNA::setSsgResampler(std::unique_ptr resampler) { std::lock_guard lg(mutex_); resampler_[SSG] = std::move(resampler); initResampler(); } void OPNA::connectToRealChip(RealChipInterfaceType type, RealChipInterfaceGeneratorFunc* f) { switch (type) { default: // Fall through case RealChipInterfaceType::NONE: if (rcIntf_->getType() != RealChipInterfaceType::NONE) rcIntf_ = std::make_unique(); rcIntf_->createInstance(f); break; #ifdef USE_REAL_CHIP case RealChipInterfaceType::SCCI: if (rcIntf_->getType() != RealChipInterfaceType::SCCI) rcIntf_ = std::make_unique(); rcIntf_->createInstance(f); break; case RealChipInterfaceType::C86CTL: if (rcIntf_->getType() != RealChipInterfaceType::C86CTL) rcIntf_ = std::make_unique(); rcIntf_->createInstance(f); break; #endif } } RealChipInterfaceType OPNA::getRealChipInterfaceType() const { return rcIntf_->getType(); } bool OPNA::hasConnectedToRealChip() const { return rcIntf_->hasConnected(); } } BambooTracker-0.6.5/BambooTracker/chip/opna.hpp000066400000000000000000000076241476276175200213730ustar00rootroot00000000000000/* * Copyright (C) 2018-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "chip.hpp" #include #include #include "resampler.hpp" #include "2608_interface.hpp" #include "real_chip_interface.hpp" namespace chip { enum class OpnaEmulator { Mame, Nuked, Ymfm, First = Mame, Last = Ymfm, }; class OPNA final : public Chip { public: // [rate] // 0 = rate is 55466 (FM synthesis rate when clock is 3993600 * 2) OPNA(OpnaEmulator emu, int clock, int rate, size_t maxDuration, size_t dramSize, std::unique_ptr fmResampler = std::make_unique(), std::unique_ptr ssgResampler = std::make_unique(), std::shared_ptr logger = nullptr); ~OPNA() override; void setImmediateWriteMode(bool enabled) noexcept; bool isImmediateWriteMode() const noexcept; void setForcedWriteMode(bool enabled) noexcept { isForcedRegWrite_ = enabled; } void setRegister(uint32_t offset, uint8_t value) override; uint8_t getRegister(uint32_t offset) const override; void setVolumeFM(double dB); double getVolumeFM() const noexcept { return volumeFm_; } void setVolumeSSG(double dB); double getVolumeSSG() const noexcept { return volumeSsg_; } size_t getDRAMSize() const noexcept; /** * @brief mix samples. * @param stream buffer where mixed samples are stored. * @param nSamples number of samples * @return true if sample generation is success, otherwise false. */ bool mix(int16_t* stream, size_t nSamples) override; void setFmResampler(std::unique_ptr resampler); void setSsgResampler(std::unique_ptr resampler); void connectToRealChip(RealChipInterfaceType type, RealChipInterfaceGeneratorFunc* f); RealChipInterfaceType getRealChipInterfaceType() const; bool hasConnectedToRealChip() const; private: static size_t count_; std::unique_ptr intf_; double volumeFm_, volumeSsg_; constexpr static int VOLUME_RATIO_MOD_ = 2; size_t dramSize_; std::unique_ptr rcIntf_; void resetSpecific() override; struct RegisterWrite { uint32_t address; uint8_t data; bool isPortA_; }; bool isForcedRegWrite_; std::deque regWrites_, forcedRegWrites_; void enqueueData(uint32_t offset, uint8_t value); void writeDataImmediately(uint32_t offset, uint8_t value); size_t dequeueData(); size_t waitRestFm_, waitRestSsg2_; size_t rate2_; sample* tmpBuf_[2]; bool storeBufferForImmediate(size_t nSamples, size_t& pointFm, size_t& pointSsg); bool storeBufferForWait(size_t nSamples, size_t& pointFm, size_t& pointSsg); void flushWait(size_t& pointFm, size_t maxFm, size_t& pointSsg2, size_t maxSsg2); struct WriteModeFuncs { void (OPNA::*setRegister)(uint32_t, uint8_t); bool (OPNA::*storeBuffer)(size_t, size_t&, size_t&); } writeFuncs[2]; WriteModeFuncs* writeFunc; }; } BambooTracker-0.6.5/BambooTracker/chip/real_chip_interface.hpp000066400000000000000000000036241476276175200244000ustar00rootroot00000000000000/* * Copyright (C) 2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include struct RealChipInterfaceGeneratorFunc { using FuncPtr = void (*)(); RealChipInterfaceGeneratorFunc(FuncPtr fp) : fp_(fp) {} FuncPtr operator()() const { return fp_; } private: FuncPtr fp_; }; enum class RealChipInterfaceType : int { NONE = 0, SCCI = 1, C86CTL = 2 }; class SimpleRealChipInterface { public: virtual ~SimpleRealChipInterface() = default; virtual bool createInstance(RealChipInterfaceGeneratorFunc* f) { if (f) delete f; return true; } virtual RealChipInterfaceType getType() const { return RealChipInterfaceType::NONE; } virtual bool hasConnected() const { return false; } virtual void reset() {} virtual void setRegister(uint32_t addr, uint8_t data) { (void)addr; (void)data; } virtual void setSSGVolume(double dB) { (void)dB; } }; BambooTracker-0.6.5/BambooTracker/chip/register_write_logger.cpp000066400000000000000000000241151476276175200250200ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "register_write_logger.hpp" #include "io/export_io.hpp" #include #include namespace chip { AbstractRegisterWriteLogger::AbstractRegisterWriteLogger(int target) : target_(target), lastWait_(0), isSetLoop_(false), loopPoint_(0), totalSampCnt_(0) { } void AbstractRegisterWriteLogger::elapse(size_t count) noexcept { lastWait_ += count; totalSampCnt_ += count; } bool AbstractRegisterWriteLogger::empty() const noexcept { return (buf_.empty() || lastWait_ != 0); } void AbstractRegisterWriteLogger::clear() noexcept { buf_.clear(); lastWait_ = 0; totalSampCnt_ = 0; isSetLoop_ = false; loopPoint_ = 0; } std::vector AbstractRegisterWriteLogger::getData() { if (lastWait_) setWait(); return buf_; } size_t AbstractRegisterWriteLogger::getSampleLength() const noexcept { return static_cast(totalSampCnt_); } size_t AbstractRegisterWriteLogger::setLoopPoint() { if (lastWait_) setWait(); isSetLoop_ = true; return loopPoint_; } size_t AbstractRegisterWriteLogger::forceMoveLoopPoint() noexcept { loopPoint_ = buf_.size(); return loopPoint_; } //******************************// VgmLogger::VgmLogger(int target, uint32_t intrRate) : AbstractRegisterWriteLogger(target), intrRate_(intrRate) {} void VgmLogger::recordRegisterChange(uint32_t offset, uint8_t value) { if (lastWait_) setWait(); const int fm = target_ & io::Export_FmMask; const int ssg = target_ & io::Export_SsgMask; const uint8_t cmdSsg = (ssg != io::Export_InternalSsg) ? 0xa0 : (fm == io::Export_YM2608) ? 0x56 : (fm == io::Export_YM2203) ? 0x55 : (fm == io::Export_YM2610B) ? 0x58 : 0x00; const uint8_t cmdFmPortA = (fm == io::Export_YM2608) ? 0x56 : (fm == io::Export_YM2612) ? 0x52 : (fm == io::Export_YM2203) ? 0x55 : (fm == io::Export_YM2610B) ? 0x58 : 0x00; const uint8_t cmdFmPortB = (fm == io::Export_YM2608) ? 0x57 : (fm == io::Export_YM2612) ? 0x53 : (fm == io::Export_YM2610B) ? 0x59 : 0x00; if (cmdSsg && offset < 0x10) { buf_.push_back(cmdSsg); buf_.push_back(static_cast(offset)); buf_.push_back(value); } else if (cmdFmPortA && (offset & 0x100) == 0) { bool compatible = true; if (offset == 0x28) { // Key register if (fm == io::Export_YM2203 && (value & 7) >= 3) compatible = false; } else if (offset == 0x29) // Mode register compatible = fm == io::Export_YM2608; else if ((offset & 0xf0) == 0x10) // Rhythm section compatible = fm == io::Export_YM2608; if (compatible) { buf_.push_back(cmdFmPortA); buf_.push_back(offset & 0xff); buf_.push_back(value); } } else if (cmdFmPortB && (offset & 0x100) != 0) { bool compatible = true; if (offset < 0x10) // ADPCM section compatible = fm == io::Export_YM2608; if (compatible) { buf_.push_back(cmdFmPortB); buf_.push_back(offset & 0xff); buf_.push_back(value); } } } void VgmLogger::setDataBlock(std::vector data) { buf_.push_back(0x67); buf_.push_back(0x66); buf_.push_back(0x81); size_t blockSize = data.size() + 8; buf_.push_back(blockSize & 0xff); buf_.push_back((blockSize >> 8) & 0xff); buf_.push_back((blockSize >> 16) & 0xff); buf_.push_back(blockSize >> 24); buf_.push_back(data.size() & 0xff); buf_.push_back((data.size() >> 8) & 0xff); buf_.push_back((data.size() >> 16) & 0xff); buf_.push_back(data.size() >> 24); buf_.resize(buf_.size() + 4); // Start address is 0 std::copy(data.begin(), data.end(), std::back_inserter(buf_)); } void VgmLogger::setWait() { while (lastWait_) { uint32_t sub; if (intrRate_ == 50) { if (lastWait_ > 65535) { uint32_t tmp = static_cast(lastWait_ - 65535); if (tmp <= 882) { //65535 - (882 - tmp) sub = 64653 + tmp; } else if (tmp <= 1764) { //65535 - (1764 - tmp) sub = 63771 + tmp; } else if (tmp <= 2646) { //65535 - (2646 - tmp) sub = 62889 + tmp; } else { sub = 65535; } buf_.push_back(0x61); buf_.push_back(sub & 0x00ff); buf_.push_back(static_cast(sub >> 8)); } else { if (lastWait_ <= 16) { buf_.push_back(static_cast(0x70 | (lastWait_ - 1))); } else if (lastWait_ > 2646) { buf_.push_back(0x61); buf_.push_back(lastWait_ & 0x00ff); buf_.push_back(static_cast(lastWait_ >> 8)); } else if (lastWait_ == 2646) { buf_.push_back(0x63); buf_.push_back(0x63); buf_.push_back(0x63); } else if (1764 <= lastWait_ && lastWait_ <= 1780) { uint32_t tmp = static_cast(lastWait_ - 1764); buf_.push_back(0x63); buf_.push_back(0x63); if (tmp) buf_.push_back(0x70 | (tmp - 1)); } else if (882 <= lastWait_ && lastWait_ <= 898) { uint32_t tmp = static_cast(lastWait_ - 882); buf_.push_back(0x63); if (tmp) buf_.push_back(0x70 | (tmp - 1)); } else { buf_.push_back(0x61); buf_.push_back(lastWait_ & 0x00ff); buf_.push_back(static_cast(lastWait_ >> 8)); } sub = static_cast(lastWait_); } } else if (intrRate_ == 60) { if (lastWait_ > 65535) { uint32_t tmp = static_cast(lastWait_ - 65535); if (tmp <= 735) { //65535 - (735 - tmp) sub = 64800 + tmp; } else if (tmp <= 1470) { //65535 - (1470 - tmp) sub = 64065 + tmp; } else if (tmp <= 2205) { //65535 - (2205 - tmp) sub = 63330 + tmp; } else { sub = 65535; } buf_.push_back(0x61); buf_.push_back(sub & 0x00ff); buf_.push_back(sub >> 8); } else { if (lastWait_ <= 16) { buf_.push_back(static_cast(0x70 | (lastWait_ - 1))); } else if (lastWait_ > 2205) { buf_.push_back(0x61); buf_.push_back(lastWait_ & 0x00ff); buf_.push_back(static_cast(lastWait_ >> 8)); } else if (lastWait_ == 2205) { buf_.push_back(0x62); buf_.push_back(0x62); buf_.push_back(0x62); } else if (1470 <= lastWait_ && lastWait_ <= 1486) { uint32_t tmp = static_cast(lastWait_ - 1470); buf_.push_back(0x62); buf_.push_back(0x62); if (tmp) buf_.push_back(0x70 | (tmp - 1)); } else if (735 <= lastWait_ && lastWait_ <= 751) { uint32_t tmp = static_cast(lastWait_ - 735); buf_.push_back(0x62); if (tmp) buf_.push_back(0x70 | (tmp - 1)); } else { buf_.push_back(0x61); buf_.push_back(lastWait_ & 0x00ff); buf_.push_back(static_cast(lastWait_ >> 8)); } sub = static_cast(lastWait_); } } else { if (lastWait_ > 65535) { sub = 65535; buf_.push_back(0x61); buf_.push_back(sub & 0x00ff); buf_.push_back(sub >> 8); } else { buf_.push_back(0x61); buf_.push_back(lastWait_ & 0x00ff); buf_.push_back(static_cast(lastWait_ >> 8)); } sub = static_cast(lastWait_); } lastWait_ -= sub; } if (!isSetLoop_) loopPoint_ = buf_.size(); } //******************************// S98Logger::S98Logger(int target) : AbstractRegisterWriteLogger(target) {} void S98Logger::recordRegisterChange(uint32_t offset, uint8_t value) { if (lastWait_) setWait(); const int fm = target_ & io::Export_FmMask; const int ssg = target_ & io::Export_SsgMask; const uint8_t cmdSsg = (ssg != io::Export_InternalSsg) ? (fm == io::Export_NoneFm) ? 0x01 : 0x02 : (fm == io::Export_YM2608) ? 0x00 : (fm == io::Export_YM2203) ? 0x00 : 0xff; const uint8_t cmdFmPortA = (fm != io::Export_NoneFm) ? 0x00 : 0xff; const uint8_t cmdFmPortB = (fm == io::Export_YM2608 || fm == io::Export_YM2612) ? 0x01 : 0xff; if (cmdSsg != 0xff && offset < 0x10) { buf_.push_back(cmdSsg); buf_.push_back(offset); buf_.push_back(value); } else if (cmdFmPortA != 0xff && (offset & 0x100) == 0) { bool compatible = true; if (offset == 0x28) { // Key register if (fm == io::Export_YM2203 && (value & 7) >= 3) compatible = false; } else if (offset == 0x29) // Mode register compatible = fm == io::Export_YM2608; else if ((offset & 0xf0) == 0x10) // Rhythm section compatible = fm == io::Export_YM2608; if (compatible) { buf_.push_back(cmdFmPortA); buf_.push_back(offset & 0xff); buf_.push_back(value); } } else if (cmdFmPortB != 0xff && (offset & 0x100) != 0) { bool compatible = true; if (offset < 0x10) // ADPCM section compatible = fm == io::Export_YM2608; if (compatible) { buf_.push_back(cmdFmPortB); buf_.push_back(offset & 0xff); buf_.push_back(value); } } } void S98Logger::setWait() { if (lastWait_ == 1) { buf_.push_back(0xff); } else { buf_.push_back(0xfe); lastWait_ -= 2; do { uint8_t b = lastWait_ & 0x7f; lastWait_ >>= 7; if (lastWait_ > 0) b |= 0x80; buf_.push_back(b); } while (lastWait_ > 0); } if (!isSetLoop_) loopPoint_ = buf_.size(); lastWait_ = 0; } } BambooTracker-0.6.5/BambooTracker/chip/register_write_logger.hpp000066400000000000000000000044531476276175200250300ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include namespace chip { class AbstractRegisterWriteLogger { public: explicit AbstractRegisterWriteLogger(int target); virtual ~AbstractRegisterWriteLogger() = default; virtual void recordRegisterChange(uint32_t offset, uint8_t value) = 0; void elapse(size_t count) noexcept; bool empty() const noexcept; void clear() noexcept; std::vector getData(); size_t getSampleLength() const noexcept; size_t setLoopPoint(); size_t forceMoveLoopPoint() noexcept; protected: int target_; std::vector buf_; uint64_t lastWait_; bool isSetLoop_; uint32_t loopPoint_; virtual void setWait() = 0; private: uint64_t totalSampCnt_; }; class VgmLogger final : public AbstractRegisterWriteLogger { public: VgmLogger(int target, uint32_t intrRate); void recordRegisterChange(uint32_t offset, uint8_t value) override; void setDataBlock(std::vector data); private: uint32_t intrRate_; void setWait() override; }; class S98Logger final : public AbstractRegisterWriteLogger { public: explicit S98Logger(int target); void recordRegisterChange(uint32_t offset, uint8_t value) override; private: void setWait() override; }; } BambooTracker-0.6.5/BambooTracker/chip/resampler.cpp000066400000000000000000000102551476276175200224150ustar00rootroot00000000000000/* * Copyright (C) 2018-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "resampler.hpp" #include #include #include "./blip_buf/blip_buf.h" namespace chip { AbstractResampler::AbstractResampler() { for (int pan = STEREO_LEFT; pan <= STEREO_RIGHT; ++pan) { destBuf_[pan] = new sample[CHIP_SMPL_BUF_SIZE_](); } } AbstractResampler::~AbstractResampler() { for (int pan = STEREO_LEFT; pan <= STEREO_RIGHT; ++pan) { delete[] destBuf_[pan]; } } void AbstractResampler::init(int srcRate, int destRate, size_t maxDuration) { srcRate_ = srcRate; maxDuration_ = maxDuration; destRate_ = destRate; updateRateRatio(); } void AbstractResampler::setMaxDuration(size_t maxDuration) noexcept { maxDuration_ = maxDuration; } /****************************************/ sample** LinearResampler::interpolate(sample** src, size_t nSamples, size_t) { if (srcRate_ == destRate_) return src; // Linear interplation for (int pan = STEREO_LEFT; pan <= STEREO_RIGHT; ++pan) { for (size_t n = 0; n < nSamples; ++n) { float curnf = n * rateRatio_; int curni = static_cast(curnf); float sub = curnf - curni; if (sub) { destBuf_[pan][n] = static_cast(src[pan][curni] + (src[pan][curni + 1] - src[pan][curni]) * sub); } else /* if (sub == 0) */ { destBuf_[pan][n] = src[pan][curni]; } } } return destBuf_; } /****************************************/ BlipResampler::BlipResampler(bool fast) { addDelta = fast ? blip_add_delta_fast : blip_add_delta; for (int pan = STEREO_LEFT; pan <= STEREO_RIGHT; ++pan) { ch_[pan].blipBuf_ = blip_new(CHIP_SMPL_BUF_SIZE_); } } BlipResampler::~BlipResampler() { for (int pan = STEREO_LEFT; pan <= STEREO_RIGHT; ++pan) { blip_delete(ch_[pan].blipBuf_); } } void BlipResampler::init(int srcRate, int destRate, size_t maxDuration) { AbstractResampler::init(srcRate, destRate, maxDuration); for (int pan = STEREO_LEFT; pan <= STEREO_RIGHT; ++pan) { auto& ch = ch_[pan]; blip_clear(ch.blipBuf_); blip_set_rates(ch.blipBuf_, srcRate, destRate); ch.prevSample_ = 0; } } void BlipResampler::reset() { for (int pan = STEREO_LEFT; pan <= STEREO_RIGHT; ++pan) { blip_clear(ch_[pan].blipBuf_); } } size_t BlipResampler::calculateInternalSampleSize(size_t nSamples, bool& ok) { if (srcRate_ == destRate_) { ok = true; return nSamples; } else { int clocks = blip_clocks_needed(ch_[0].blipBuf_, nSamples - blip_samples_avail(ch_[0].blipBuf_)); ok = clocks >= 0; return clocks; } } sample** BlipResampler::interpolate(sample** src, size_t nSamples, size_t intrSize) { if (srcRate_ == destRate_) return src; short tmpBuf[CHIP_SMPL_BUF_SIZE_]; for (int pan = STEREO_LEFT; pan <= STEREO_RIGHT; ++pan) { auto& ch = ch_[pan]; if (ch.blipBuf_ == nullptr) continue; auto srcBuf = src[pan]; for (size_t i = 0; i < intrSize; ++i) { addDelta(ch.blipBuf_, i, srcBuf[i] - ch.prevSample_); ch.prevSample_ = srcBuf[i]; } blip_end_frame(ch.blipBuf_, intrSize); size_t size = std::min(nSamples, blip_samples_avail(ch.blipBuf_)); blip_read_samples(ch.blipBuf_, tmpBuf, size, 0); std::copy_n(std::begin(tmpBuf), size, destBuf_[pan]); } return destBuf_; } } BambooTracker-0.6.5/BambooTracker/chip/resampler.hpp000066400000000000000000000057431476276175200224300ustar00rootroot00000000000000/* * Copyright (C) 2018-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "chip_defs.h" #include #include struct blip_t; namespace chip { enum class ResamplerType : int { Linear = 0, BlipBuf, FastBlipBuf, }; class AbstractResampler { public: AbstractResampler(); virtual ~AbstractResampler(); virtual void init(int srcRate, int destRate, size_t maxDuration); virtual void reset() {} virtual void setDestributionRate(int destRate) { init(srcRate_, destRate, maxDuration_); } virtual void setMaxDuration(size_t maxDuration) noexcept; virtual sample** interpolate(sample** src, size_t nSamples, size_t intrSize) = 0; /** * @brief calculateInternalSampleSize * @param nSamples number of samples after resampling * @param ok false is stored if something went wrong during calculation, otherwise true * @return number of samples to resmple */ virtual size_t calculateInternalSampleSize(size_t nSamples, bool& ok) { ok = true; return static_cast(std::ceil(nSamples * rateRatio_)); } protected: int srcRate_, destRate_; size_t maxDuration_; float rateRatio_; sample* destBuf_[2]; void updateRateRatio() { rateRatio_ = static_cast(srcRate_) / destRate_; } }; class LinearResampler final : public AbstractResampler { public: sample** interpolate(sample** src, size_t nSamples, size_t) override; }; class BlipResampler final : public AbstractResampler { public: BlipResampler(bool fast = false); ~BlipResampler(); void init(int srcRate, int destRate, size_t maxDuration) override; void reset() override; void setDestributionRate(int destRate) override { init(srcRate_, destRate, maxDuration_); } size_t calculateInternalSampleSize(size_t nSamples, bool& ok) override; sample** interpolate(sample** src, size_t nSamples, size_t intrSize) override; private: struct Channel { blip_t* blipBuf_; short prevSample_; } ch_[2]; void (*addDelta)(blip_t*, unsigned int, int); }; } BambooTracker-0.6.5/BambooTracker/chip/scci/000077500000000000000000000000001476276175200206355ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/chip/scci/SCCIDefines.hpp000066400000000000000000000100401476276175200233600ustar00rootroot00000000000000//---------------------------------------------------------------------- // SCCI Sound Interfaces defines //---------------------------------------------------------------------- #pragma once namespace scci { // Sound chip list enum SC_CHIP_TYPE { SC_TYPE_NONE = 0, SC_TYPE_YM2608, SC_TYPE_YM2151, SC_TYPE_YM2610, SC_TYPE_YM2203, SC_TYPE_YM2612, SC_TYPE_AY8910, SC_TYPE_SN76489, SC_TYPE_YM3812, SC_TYPE_YMF262, SC_TYPE_YM2413, SC_TYPE_YM3526, SC_TYPE_YMF288, SC_TYPE_SCC, SC_TYPE_SCCS, SC_TYPE_Y8950, SC_TYPE_YM2164, // OPP:OPMとはハードウェアLFOの制御が違う SC_TYPE_YM2414, // OPZ:OPMとピンコンパチ SC_TYPE_AY8930, // APSG:拡張PSG SC_TYPE_YM2149, // SSG:PSGとはDACが違う(YM3439とは同一とみていいと思う) SC_TYPE_YMZ294, // SSGL:SSGとはDACが違う(YMZ284とは同一とみていいと思う) SC_TYPE_SN76496, // DCSG:76489とはノイズジェネレータの生成式が違う SC_TYPE_YM2420, // OPLL2:OPLLとはFnumの設定方法が違う。音は同じ。 SC_TYPE_YMF281, // OPLLP:OPLLとは内蔵ROM音色が違う。制御は同じ。 SC_TYPE_YMF276, // OPN2L:OPN2/OPN2CとはDACが違う SC_TYPE_YM2610B, // OPNB-B:OPNBとはFM部のch数が違う。 SC_TYPE_YMF286, // OPNB-C:OPNBとはDACが違う。 SC_TYPE_YM2602, // 315-5124: 76489/76496とはノイズジェネレータの生成式が違う。POWON時に発振しない。 SC_TYPE_UM3567, // OPLLのコピー品(だけどDIP24なのでそのままリプレースできない) SC_TYPE_YMF274, // OPL4:試作未定 SC_TYPE_YM3806, // OPQ:試作予定 SC_TYPE_YM2163, // DSG:試作中 SC_TYPE_YM7129, // OPK2:試作中 SC_TYPE_YMZ280, // PCM8:ADPCM8ch:試作予定 SC_TYPE_YMZ705, // SSGS:SSG*2set+ADPCM8ch:試作中 SC_TYPE_YMZ735, // FMS:FM8ch+ADPCM8ch:試作中 SC_TYPE_YM2423, // YM2413の音色違い SC_TYPE_SPC700, // SPC700 SC_TYPE_NBV4, // NBV4用 SC_TYPE_AYB02, // AYB02用 SC_TYPE_8253, // i8253(及び互換チップ用) SC_TYPE_315_5124, // DCSG互換チップ SC_TYPE_SPPCM, // SPPCM SC_TYPE_C140, // NAMCO C140(SPPCMデバイス) SC_TYPE_SEGAPCM, // SEGAPCM(SPPCMデバイス) SC_TYPE_SPW, // SPW SC_TYPE_SAM2695, // SAM2695 SC_TYPE_MIDI, // MIDIインターフェース SC_TYPE_MAX, // 使用可能デバイスMAX値 // 以降は、専用ハード用 // 実験ハード用 SC_TYPE_OTHER = 1000, // その他デバイス用、アドレスがA0-A3で動作する SC_TYPE_UNKNOWN, // 開発デバイス向け SC_TYPE_YMF825, // YMF825(暫定) }; // Sound chip clock list enum SC_CHIP_CLOCK { SC_CLOCK_NONE = 0, SC_CLOCK_1789773 = 1789773, // SSG,OPN,OPM,SN76489 etc SC_CLOCK_1996800 = 1996800, // SSG,OPN,OPM,SN76489 etc SC_CLOCK_2000000 = 2000000, // SSG,OPN,OPM,SN76489 etc SC_CLOCK_2048000 = 2048000, // SSGLP(4096/2|6144/3) SC_CLOCK_3579545 = 3579545, // SSG,OPN,OPM,SN76489 etc SC_CLOCK_3993600 = 3993600, // OPN(88) SC_CLOCK_4000000 = 4000000, // SSF,OPN,OPM etc SC_CLOCK_7159090 = 7159090, // OPN,OPNA,OPNB,OPN2,OPN3L etc SC_CLOCK_7670454 = 7670454, // YM-2612 etc SC_CLOCK_7987200 = 7987200, // OPNA(88) SC_CLOCK_8000000 = 8000000, // OPNB etc SC_CLOCK_10738635 = 10738635, // 315-5124 SC_CLOCK_12500000 = 12500000, // RF5C164 SC_CLOCK_14318180 = 14318180, // OPL2 SC_CLOCK_16934400 = 16934400, // YMF271 SC_CLOCK_23011361 = 23011361, // PWM }; // Sound chip location enum SC_CHIP_LOCATION { SC_LOCATION_MONO = 0, SC_LOCATION_LEFT = 1, SC_LOCATION_RIGHT = 2, SC_LOCATION_STEREO = 3 }; // mode defines #define SC_MODE_ASYNC (0x00000000) #define SC_MODE_SYNC (0x00000001) // sound chip Acquisition mode defines #define SC_ACQUISITION_MODE_NEAR (0x00000000) #define SC_ACQUISITION_MODE_MATCH (0x00000001) #define SC_WAIT_REG (0xffffffff) // ウェイとコマンド送信(データは送信するコマンド数) #define SC_FLUSH_REG (0xfffffffe) // 書き込みデータフラッシュ待ち #define SC_DIRECT_BUS (0x80000000) // アドレスバスダイレクトモード } BambooTracker-0.6.5/BambooTracker/chip/scci/scci.hpp000066400000000000000000000131731476276175200222740ustar00rootroot00000000000000//---------------------------------------------------------------------- // Sound Chip common Interface //---------------------------------------------------------------------- #pragma once /*#include */ #include #ifndef _WIN32 #define __stdcall #endif namespace scci { using DWORD = uint32_t; using BOOL = bool; using BYTE = uint8_t; // Sound Interface Infomation typedef struct { char cInterfaceName[64]; // Interface Name int iSoundChipCount; // Sound Chip Count; } SCCI_INTERFACE_INFO; // Sound Chip Infomation typedef struct { char cSoundChipName[64]; // Sound Chip Name int iSoundChip; // Sound Chip ID int iCompatibleSoundChip[2]; // Compatible Sound Chip ID DWORD dClock; // Sound Chip clock DWORD dCompatibleClock[2]; // Sound Chip clock BOOL bIsUsed; // Sound Chip Used Check DWORD dBusID; // 接続バスID DWORD dSoundLocation; // サウンドロケーション } SCCI_SOUND_CHIP_INFO; class SoundInterfaceManager; class SoundInterface; class SoundChip; //---------------------------------------- // Sound Interface Manager //---------------------------------------- class SoundInterfaceManager{ public: // ---------- LOW LEVEL APIs ---------- // get interface count virtual int __stdcall getInterfaceCount() = 0; // get interface information virtual SCCI_INTERFACE_INFO* __stdcall getInterfaceInfo(int iInterfaceNo) = 0; // get interface instance virtual SoundInterface* __stdcall getInterface(int iInterfaceNo) = 0; // release interface instance virtual BOOL __stdcall releaseInterface(SoundInterface* pSoundInterface) = 0; // release all interface instance virtual BOOL __stdcall releaseAllInterface() = 0; // ---------- HI LEVEL APIs ---------- // get sound chip instance virtual SoundChip* __stdcall getSoundChip(int iSoundChipType,DWORD dClock) = 0; // release sound chip instance virtual BOOL __stdcall releaseSoundChip(SoundChip* pSoundChip) = 0; // release all sound chip instance virtual BOOL __stdcall releaseAllSoundChip() = 0; // set delay time virtual BOOL __stdcall setDelay(DWORD dMSec) = 0; // get delay time virtual DWORD __stdcall getDelay() = 0; // reset interfaces(A sound chips initialize after interface reset) virtual BOOL __stdcall reset() = 0; // initialize sound chips virtual BOOL __stdcall init() = 0; // Sound Interface instance initialize virtual BOOL __stdcall initializeInstance() = 0; // Sound Interface instance release virtual BOOL __stdcall releaseInstance() = 0; // config scci // !!!this function is scciconfig exclusive use!!! virtual BOOL __stdcall config() = 0; // get version info /*virtual DWORD __stdcall getVersion(DWORD *pMVersion = NULL) = 0;*/ virtual DWORD __stdcall getVersion(DWORD *pMVersion = nullptr) = 0; // get Level mater disp valid virtual BOOL __stdcall isValidLevelDisp() = 0; // get Level mater disp visible virtual BOOL __stdcall isLevelDisp() = 0; // set Level mater disp visible virtual void __stdcall setLevelDisp(BOOL bDisp) = 0; // set mode virtual void __stdcall setMode(int iMode) = 0; // send datas virtual void __stdcall sendData() = 0; // clear buffer virtual void __stdcall clearBuff() = 0; // set Acquisition Mode(Sound Chip) virtual void __stdcall setAcquisitionMode(int iMode) = 0; // set Acquisition Mode clock renge virtual void __stdcall setAcquisitionModeClockRenge(DWORD dClock) = 0; // set command buffer size virtual BOOL __stdcall setCommandBuffetSize(DWORD dBuffSize) = 0; // buffer check virtual BOOL __stdcall isBufferEmpty() = 0; }; //---------------------------------------- // Sound Interface(LOW level APIs) //---------------------------------------- class SoundInterface{ public: // support low level API check virtual BOOL __stdcall isSupportLowLevelApi() = 0; // send data to interface virtual BOOL __stdcall setData(BYTE *pData,DWORD dSendDataLen) = 0; // get data from interface virtual DWORD __stdcall getData(BYTE *pData,DWORD dGetDataLen) = 0; // set delay time virtual BOOL __stdcall setDelay(DWORD dDelay) = 0; // get delay time virtual DWORD __stdcall getDelay() = 0; // reset interface virtual BOOL __stdcall reset() = 0; // initialize sound chips virtual BOOL __stdcall init() = 0; // サウンドチップ数取得 virtual DWORD __stdcall getSoundChipCount() = 0; // サウンドチップ取得 virtual SoundChip* __stdcall getSoundChip(DWORD dNum) = 0; }; //---------------------------------------- // Sound Chip //---------------------------------------- class SoundChip{ public: // get sound chip information virtual SCCI_SOUND_CHIP_INFO* __stdcall getSoundChipInfo() = 0; // get sound chip type virtual int __stdcall getSoundChipType() = 0; // set Register data virtual BOOL __stdcall setRegister(DWORD dAddr,DWORD dData) = 0; // get Register data(It may not be supported) virtual DWORD __stdcall getRegister(DWORD dAddr) = 0; // initialize sound chip(clear registers) virtual BOOL __stdcall init() = 0; // get sound chip clock virtual DWORD __stdcall getSoundChipClock() = 0; // get writed register data virtual DWORD __stdcall getWrittenRegisterData(DWORD addr) = 0; // buffer check virtual BOOL __stdcall isBufferEmpty() = 0; }; //---------------------------------------- // get sound interface manager function //---------------------------------------- typedef SoundInterfaceManager* (__stdcall *SCCIFUNC)(void); //---------------------------------------- // pcm callback function // void callback(SCCIPCMDATA *pPcm,DWORD dSize) //---------------------------------------- typedef struct { int iL; int iR; } SCCIPCMDATA; } BambooTracker-0.6.5/BambooTracker/chip/scci/scci_wrapper.cpp000066400000000000000000000041731476276175200240270ustar00rootroot00000000000000/* * Copyright (C) 2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "scci_wrapper.hpp" #include "scci.hpp" #include "SCCIDefines.hpp" Scci::Scci() : man_(nullptr), chip_(nullptr) {} Scci::~Scci() { if (chip_) man_->releaseSoundChip(chip_); if (man_) man_->releaseInstance(); } bool Scci::createInstance(RealChipInterfaceGeneratorFunc* f) { auto getManager = f ? reinterpret_cast((*f)()) : nullptr; if (f) delete f; auto man = getManager ? getManager() : nullptr; if (man) { man_ = man; man_->initializeInstance(); man_->reset(); chip_ = man_->getSoundChip(scci::SC_TYPE_YM2608, scci::SC_CLOCK_7987200); if (!chip_) { man_->releaseInstance(); man_ = nullptr; return false; } return true; } else { if (!chip_) return false; man_->releaseSoundChip(chip_); chip_ = nullptr; man_->releaseInstance(); man_ = nullptr; return false; } } bool Scci::hasConnected() const { return (man_ != nullptr); } void Scci::reset() { if (chip_) chip_->init(); } void Scci::setRegister(uint32_t addr, uint8_t data) { if (chip_) chip_->setRegister(addr, data); } BambooTracker-0.6.5/BambooTracker/chip/scci/scci_wrapper.hpp000066400000000000000000000032361476276175200240330ustar00rootroot00000000000000/* * Copyright (C) 2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include "../real_chip_interface.hpp" namespace scci { class SoundInterfaceManager; class SoundChip; } class Scci final : public SimpleRealChipInterface { public: Scci(); ~Scci() override; bool createInstance(RealChipInterfaceGeneratorFunc* f) override; RealChipInterfaceType getType() const override { return RealChipInterfaceType::SCCI; } bool hasConnected() const override; void reset() override; void setRegister(uint32_t addr, uint8_t data) override; private: scci::SoundInterfaceManager* man_; scci::SoundChip* chip_; }; BambooTracker-0.6.5/BambooTracker/chip/ymfm/000077500000000000000000000000001476276175200206645ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/chip/ymfm/GeneralInfo.md000066400000000000000000000355501476276175200234070ustar00rootroot00000000000000# ymfm: One FM core to rule them all The ymfm emulator ws written from the ground-up using the analysis and deduction by Nemesis as a starting point, particularly in [this thread](https://gendev.spritesmind.net/forum/viewtopic.php?f=24&t=386). The core assumption is that these details apply to all FM variants unless otherwise proven incorrect. The fine details of this implementation have also been cross-checked against Nemesis' implementation in his [Exodus emulator](https://www.exodusemulator.com/), as well as Alexey Khokholov's ["Nuked" implementations](https://github.com/nukeykt/Nuked-OPN2) based off die shots. Operator and channel summing/mixing code for OPM and OPN is largely based off of research done by [David Viens](https://twitter.com/plgDavid) and Hubert Lamontagne. ## Families The Yamaha FM chips can be broadly categoried into families: * OPM (YM2151) * OPP (YM2164) * OPN (YM2203) * OPNA/OPNB/OPN2 (YM2608, YM2610, YM2610B, YM2612, YM3438, YMF276, YMF288) * OPL (YM3526) * OPL2 (YM3812) * OPLL (YM2413, YM2423, YMF281, DS1001, and others) * OPL3 (YMF262, YMF289B) * OPL4 (YMF278) Additionally, several lesser-documented variants exist exclusively in the employ of Yamaha synthesizers: * OPQ (YM3608) * OPZ (YM2414) All of these families are very closely related, and the ymfm engine is designed to be universal to work across all of these families. Of course, each variant has its own register maps, features, and implementation details which need to be sorted out. Thus, each significant variant listed above is represented by a register class. The register class contains: * constants describing core parameters and features * mappers between operators and channels * generic fetchers that return normalized values across families * family-specific implementations of LFO and phase calculations ## Family History This history outlines the progress of adding/removing features across the three main families (OPM, OPN, OPL): OPM started it all off, featuring: * 8 FM channels, 4 operators each * LFO and noise support * Stereo output OPM -> OPN changes: * Reduced to 3 FM channels, 4 operators each * Removed LFO and noise support * Mono output * Integrated AY-8910 compatible PSG * Added SSG-EG envelope mode * Added multi-frequency mode: ch. 3 operators can have separate frequencies * Software controlled clock divider OPN -> OPNA changes: * Increased to 6 FM channels, 4 operators each * Added back (a cut-down) LFO * Stereo output again * Removed software controlled divider on later versions (OPNB/OPN2) * Removed PSG on OPN2 models OPNA -> OPL changes: * Increased to 9 FM channels, but only 2 operators each * Even more simplified LFO * Mono output * Removed PSG * Removed SSG-EG envelope modes * Removed multi-frequency modes * Fixed clock divider * Built-in ryhthm generation OPL -> OPL2 changes: * Added 4 selectable waveforms OPL2 -> OPLL changes: * Vastly simplified register map * 15 built-in instruments, plus built-in rhythm instruments * 1 user-controlled instrument OPL2 -> OPL3 changes: * Increased to 18 FM channels, 2 operators each * 4 output channels * Increased to 8 selectable waveforms * 6 channels can be configured to use 4 operators ## Channels and Operators The polyphony of a given chip is determined by the number of channels it supports. This number ranges from as low as 3 to as high as 18. Each channel has either 2 or 4 operators that can be combined in a myriad of ways. On most chips the number of operators per channel is fixed; however, some later OPL chips allow this to be toggled between 2 and 4 at runtime. The base ymfm engine class maintains an array of channels and operators, while the relationship between the two is described by the register class. ## Registers Registers on the Yamaha chips are generally write-only, and can be divided into three distinct categories: * system-wide registers * channel-specific registers * operator-specific registers For maximum flexibility, most parameters can be configured at the operator level, with channel-level registers controlling details such as how to combine the operators into the final output. System-wide registers are used to control chip-wide modes and manage onboard timer functions. Note that since registers are write-only, some ymfm register classes will use "holes" in the register space to store additional values that may be needed. ## Attenuation Most of the computations of the FM engines are done in terms of attenuation, and thus are logarithmic in nature. The maximum resolution used internally is 12 bits, as returned by the sin table: Bit | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 ----|----|----|----|----|----|----|-----|------|-------|--------|---------|--------- dB | -96| -48| -24| -12| -6| -3| -1.5| -0.75| -0.375| -0.1875| -0.09375| -0.046875 The envelope generator internally uses 10 bits: Bit | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | ----|----|----|----|----|----|-----|------|-------|--------|---------| dB | -48| -24| -12| -6| -3| -1.5| -0.75| -0.375| -0.1875| -0.09375| Total level for operators is usually represented by 7 bits: Bit | 6 | 5 | 4 | 3 | 2 | 1 | 0 | ----|----|----|----|----|----|-----|------| dB | -48| -24| -12| -6| -3| -1.5| -0.75| Sustain level in the envelope generator is usually represented by 4 bits: Bit | 3 | 2 | 1 | 0 | ----|----|----|----|----| dB | -24| -12| -6| -3| ## Status and Timers Generically, all chips (except OPLL) support two timers that can be programmed to fire and signal IRQs. These timers also set bits in the status register. The behavior of these bits is shared across all implementations, even if the exact bit positions shift (this is controlled by constants in the registers class). In addition, several chips incorporate ADPCM decoders which also may set bits in the same status register. For this reason, it is possible to control various bits in the status register via the `set_reset_status()` function directly. Any active bits that are set and which are not masked (mask is controlled by `set_irq_mask()`), lead to an IRQ being signalled. Thus, it is possible for the chip-specific implementations to set the mask and control the status register bits such that IRQs are signalled via the same mechanism as timer signals. In addition, the OPM and OPN families have a "busy" flag, which is set after each write, indicating that another write should not be performed. Historically, the duration of this flag was constant and had nothing to do with the internals of the chip. However, since the details can potentially vary chip-to-chip, it is the chip's responsibility to insert the busy flag into the status before returning it to the caller. ## Clocking Each of the Yamaha chips works by cycling through all operators one at a time. Thus, the effective output rate of the chips is related to the input clock divided by the number of operators. In addition, the input clock is prescaled by an amount. Generally, this is a fixed value, though some early OPN chips allow this to be selected at runtime from a small number of values. ## Channel Frequencies One major difference between OPM and later families is in how frequencies are specified. OPM specifies frequency via a 3-bit 'block' (aka octave), combined with a 4-bit 'key code' (note number) and a 6-bit 'key fraction'. The key code and fraction are converted on the chip into an x.11 fixed-point value and then shifted by the block to produce the final step value for the phase. Later families, on the other hand, specify frequencies via a 3-bit 'block' just as on OPM, but combined with a 9-12-bit 'frequency number' or 'fnum', which is directly shifted by the block to produce the step value. So essentially, later chips make the user do the conversion from note value to phase increment, while OPM is programmed in a more 'musical' way, specifying notes and cents. Internally, this is abstracted away into a 'block_freq' value, which is a 16-bit value containing the block and frequency info concatenated together as follows: * OPM: `[3-bit block]:[4-bit keycode]:[6-bit fraction] = 13 bits total` * OPZ: `[3-bit block]:[12-bit fnum] = 15 bits total` * OPN: `[3-bit block]:[11-bit fnum] 0 = 15 bits total` * OPL: `[3-bit block]:[10-bit fnum]:00 = 15 bits total` * OPLL: `[3-bit block]:[ 9-bit fnum]:000 = 15 bits total` The register classes handle the raw format directly and convert it into a phase increment which can be used by the generic engine. ## Low Frequency Oscillator (LFO) The LFO engines are different in several key ways. The OPM LFO engine is fairly intricate. It has a 4.4 floating-point rate which allows for a huge range of frequencies, and can select between four different waveforms (sawtooth, square, triangle, or noise). Separate 7-bit depth controls for AM and PM control the amount of modulation applied in each case. This global LFO value is then further controlled at the channel level by a 2-bit AM sensitivity and a 3-bit PM sensitivity, and each operator has a 1-bit AM on/off switch. For OPN the LFO engine was removed entirely, but a limited version was put back in OPNA and later chips. This stripped-down version offered only a 3-bit rate setting (versus the 4.4 floating-point rate in OPN), and no global depth control. It did bring back the channel-level sensitivity controls and the operator-level on/off control. For OPL, the LFO is simplified again, with AM and PM running at fixed frequencies, and simple enable flags at the operator level for each controlling their application. ## Differences Between Families The table below provides some high level functional differences between the differnet families: subfamily: | OPM | OPN | OPNA | OPL | OPL2 | OPLL | OPL3 | ------------:|:------:|:------:|:------:|:------:|:------:|:------:|:------:| outputs: | 2 | 1 | 2 | 1 | 1 | 1 | 4 | channels: | 8 | 3 | 6 | 9 | 9 | 9 | 18 | operators: | 32 | 12 | 24 | 18 | 18 | 18 | 36 | waveforms: | 1 | 1 | 1 | 1 | 4 | 2 | 8 | instruments: | no | no | no | yes | yes | yes | yes | ryhthm: | no | no | no | no | no | yes | no | dynamic ops: | no | no | no | no | no | no | yes | prescale: | 2 | 2/3/6 | 2/3/6 | 4 | 4 | 4 | 8 | EG divider: | 3 | 3 | 3 | 1 | 1 | 1 | 1 | EG DP: | no | no | no | no | no | yes | no | EG SSG: | no | yes | yes | no | no | no | no | mod delay: | no | no | no | yes | yes | yes? | no | CSM: | yes | ch 2 | ch 2 | yes | yes | yes | no | LFO: | yes | no | yes | yes | yes | yes | yes | noise: | yes | no | no | no | no | no | no | * Outputs represents the number of output channels: 1=mono, 2=stereo, 4=stereo+. * Channels represents the number of independent FM channels. * Operators represents the number of operators, or "slots" which are assembled into the channels. * Waveforms represents the number of different sine-derived waveforms available. * Instruments indicates whether the family has built-in instruments. * Rhythm indicates whether the family has a built-in rhythm * Dynamic ops indicates whether it is possible to switch between 2-operator and 4-operator modes dynamically. * Prescale specifies the default clock divider; some chips allow this to be controlled via register writes. * EG divider represents the divider applied to the envelope generator clock. * EG DP indicates whether the envelope generator includes a DP (depress?) phase at the beginning of each key on. * SSG EG indicates whether the envelope generator has SSG-style support. * Mod delay indicates whether the connection to the first modulator's input is delayed by 1 sample. * CSM indicates whether CSM mode is supported, triggered by timer A. * LFO indicates whether LFO is supported. * Noise indicates whether one of the operators can be replaced with a noise source. ## Chip Specifics While OPM is its own thing, the OPN and OPL families have quite a few specific implementations, with many differing details beyond the core FM parts. Here are some details on the OPN family: chip ID: | YM2203 | YM2608 | YMF288 | YM2610 | YM2610B | YM2612 | YM3438 | YMF276 | ---------:|:------:|:------:|:------:|:------:|:-------:|:------:|:------:|:------:| aka: | OPN | OPNA | OPN3L | OPNB | OPNB2 | OPN2 | OPN2C | OPN2L | FM: | 3 | 6 | 6 | 4 | 6 | 6 | 6 | 6 | AY-8910: | 3 | 1 | 1 | 1 | 1 | - | - | - | ADPCM-A: | - | 6 int | 6 int | 6 ext | 6 ext | - | - | - | ADPCM-B: | - | 1 ext | - | 1 ext | 1 ext | - | - | - | DAC: | no | no | no | no | no | yes | yes | yes | output: | 10.3fp | 16-bit | 16-bit | 16-bit | 16-bit | 9-bit | 9-bit | 16-bit | summing: | adder | adder | adder | adder | adder | muxer | muxer | adder | * FM represents the number of FM channels available. * AY-8910 represents the number of AY-8910-compatible outputs. * ADPCM-A represents the number of internal/external ADPCM-A channels present. * ADPCM-B represents the number of internal/external ADPCM-B channels present. * DAC indicates if a directly-accessible DAC output exists, replacing one channel. * Output indicates the output format to the final DAC. * Summing indicates whether channels are added or time divided in the output. OPL has a similar trove of chip variants: chip ID: | YM3526 | Y8950 | YM3812 | YM2413 | YMF262 | YMF289B | YMF278B | ------------:|:------:|:-------:|:------:|:------:|:------:|:-------:|:-------:| aka: | OPL |MSX-AUDIO| OPL2 | OPLL | OPL3 | OPL3L | OPL4 | FM: | 9 | 9 | 9 | 9 | 18 | 18 | 18 | ADPCM-B: | - | 1 ext | - | - | - | - | - | wavetable: | - | - | - | - | - | - | 24 | instruments: | no | no | no | yes | no | no | no | output: | 10.3fp | 10.3fp | 10.3fp | 9-bit | 16-bit | 16-bit | 16-bit | summing: | adder | adder | adder | muxer | adder | adder | adder | * FM represents the number of FM channels available. * ADPCM-B represents the number of external ADPCM-B channels present. * Wavetable indicates the number of wavetable channels present. * Instruments indicates that the chip has built-in instrument selection. * Output indicates the output format to the final DAC. * Summing indicates whether channels are added or time divided in the output. There are several close variants of the YM2413 with different sets of built-in instruments. These include the YM2423, YMF281, and DS1001 (aka Konami VRC7). BambooTracker-0.6.5/BambooTracker/chip/ymfm/LICENSE000066400000000000000000000027571476276175200217040ustar00rootroot00000000000000BSD 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. BambooTracker-0.6.5/BambooTracker/chip/ymfm/README.md000066400000000000000000000134031476276175200221440ustar00rootroot00000000000000# ymfm
[ymfm](https://github.com/aaronsgiles/ymfm) is a collection of BSD-licensed Yamaha FM sound cores (OPM, OPN, OPL, and others), written by [Aaron Giles](https://aarongiles.com) ## Supported environments This code should compile cleanly in any environment that has C++14 support. It has been tested on gcc, clang, and Microsoft Visual C++ 2019. ## Supported chip families Currently, support is present for the following chips (organized by header file): * ymfm_misc.h: * YM2149 (SSG) [1983: MSX; Atari ST] * ymfm_opm.h: * YM2151 (OPM) [1983: Sharp X1, X68000; MSX; synths: DX21, DX27, DX100] * YM2164 (OPP) [1985: FB-01 MIDI Expander; IBM Music Feature Card; MSX; synths: Korg DS-8, 707] * ymfm_opn.h: * YM2203 (OPN) [1984: NEC PC-88, PC-98, NEC PC-6001mkII SR, PC-6601 SR] * YM2608 (OPNA) [1985: NEC PC-88, PC-98] * YM2610 (OPNB) [1987: Neo Geo] * YM2610B (OPNB2) * YM2612 (OPN2) [1988: Sega Mega Drive/Genesis; FM Towns] * YM3438 (OPN2C) * YMF276 (OPN2L) * YMF288 (OPN3L) [1995: NEC PC-98] * ymfm_opl.h: * YM3526 (OPL) [1984: C64 SFX Sound Expander] * Y8950 (MSX-Audio) [1984: MSX] * YM3812 (OPL2) [1985: AdLib, Sound Blaster; synths: some Portasound keyboards] * YMF262 (OPL3) [1988: Sound Blaster Pro 2.0, SB16] * YMF289B (OPL3L) * YMF278B (OPL4) [1993: MSX Moonsound cartridge] * YM2413 (OPLL) [1986: Sega Master System, Mark III; MSX; synths: Portasound PSS-140, PSS-170, PSS-270] * YM2423 (OPLL-X) * YMF281 (OPLLP) * DS1001 (Konami 053982/VRC7) [1991: Famicom cartridge Lagrange Point] * ymfm_opq.h: * YM3806 (OPQ) [synths: PSR-60/70] * ymfm_opz.h: * YM2414 (OPZ) [1987: synths: TX81Z, DX11, YS200; Korg Z3 guitar synth] There are some obviously-related chips that also are on my horizon but have no implementation as yet: * YMW-258-F 'GEW8' (aka Sega 315-5560 aka Sega Multi-PCM) * YMF271 (OPX) * YM21280 (OPS) / YM21290 (EGS) [synths: DX7, DX1, DX5, DX9, TX7, TX216, TX416, TX816] * OPK? ## History These cores were originally written during the summer and fall of 2020 as part of the [MAME](https://mamedev.org/) project. As such, their design started off heavily based on how MAME works. The OPM/OPN cores first appeared in MAME 0.230. The OPL cores were added in MAME 0.231. A further rewrite to abstract MAME dependencies is planned for MAME 0.232. The goal was threefold: 1. provide BSD-licensed emulation cores that are more compatible with MAME's core licensing 1. modernize and unify the code around a common implementation of shared features 1. improve accuracy where possible based on discoveries made by others ## Accuracy The goal of these cores is not 100% digital accuracy. To achieve that would require full emulation of the pipelines, which would make the code extremely difficult to comprehend. It would also make it much harder to share common implementations of features, or to add support for less well-known chip types. If you want that level of accuracy, there are [several](https://github.com/nukeykt/Nuked-OPN2) [decap-based](https://github.com/nukeykt/Nuked-OPM) [emulation cores](https://github.com/nukeykt/Nuked-OPLL) out there. Instead, the main goals are: 1. Extremely high (audibly indistinguishable) accuracy 1. Reasonable performance 1. Clean design with readable code 1. Clear documentation of the various chips ## General approach Check out the [examples directory](https://github.com/aaronsgiles/ymfm/tree/main/examples) for some example usage patterns. I'm not a big fan of makefiles for simple things, so instructions on how to compile each example are provided at the top. # IMPORTANT As of May 2021, the interface to these is still a bit in flux. Be prepared when syncing with upstream to make some adjustments. ### Clocking The general philosophy of the emulators provided here is that they are clock-independent. Much like the actual chips, you (the consumer) control the clock; the chips themselves have no idea what time it is. They just tick forward each time you ask them to. The way you move things along is via the `generate()` function, which ticks the internal system forward one or more samples, and writes out an array out chip-specific `output_data`. But what, exactly, is a "sample", and how long is it? This is where the external clock comes in. Most of the Yamaha chips are externally clocked in the MHz range. They then divide that clock by a factor (sometimes dynamically controllable), and then the internal operators are pipelined to further divide the clock. For example, the YM2151 internally divides the clock by 2, and has 32 operators to iterate through. Thus, for a nominal input lock of 3.58MHz, you end up at around a 55.9kHz sample rate. Fortunately, all the chip implementations can compute this for you; just pass the raw external clock value to the `sample_rate()` method and it will hand you back the output sample rate you want. Then call `generate()` that many times per second to output the results. But what if I want to output at a "normal" rate, like 44.1kHz? Sorry, you'll have to rate convert as needed. ### Reading and Writing To read or write to the chips, you can call the `read()` and `write()` methods. The offset provided corresponds to the addressing input lines in a (hopefully) logical way. For reads, almost all chips have a status register, which you can read via `read_status()`. Some chips have a data port that can be read via `read_data()`. And chips with extended addressing may also have `read_status_hi()` and `read_data_hi()`. For writes, almost all chips have an address register and a data register, and so you can reliably count on there being a `write_address()` and `write_data()` method as well. If the chip supports extended addressing, it may also have `write_address_hi()` and `write_data_hi()`. BambooTracker-0.6.5/BambooTracker/chip/ymfm/ymfm.h000066400000000000000000000445201476276175200220120ustar00rootroot00000000000000// 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]; std::memcpy(&header[0], "RIFF", 4); *(uint32_t *)&header[4] = m_buffer.size() * 2 + 44 - 8; std::memcpy(&header[8], "WAVE", 4); std::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; std::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 BambooTracker-0.6.5/BambooTracker/chip/ymfm/ymfm_2608.cpp000066400000000000000000000066571476276175200230350ustar00rootroot00000000000000/* * Copyright (C) 2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "ymfm_2608.hpp" extern const unsigned char YM2608_ADPCM_ROM[0x2000]; namespace chip { Ymfm2608::YmfmInterface::YmfmInterface(uint32_t dramSize) : dram_(dramSize) {} uint8_t Ymfm2608::YmfmInterface::ymfm_external_read(ymfm::access_class type, uint32_t address) { switch (type) { case ymfm::access_class::ACCESS_ADPCM_A: return address < 0x2000 ? YM2608_ADPCM_ROM[address] : 0; case ymfm::access_class::ACCESS_ADPCM_B: return address < dram_.size() ? dram_[address] : 0; default: return 0; } } void Ymfm2608::YmfmInterface::ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data) { if (type == ymfm::access_class::ACCESS_ADPCM_B && address < dram_.size()) { dram_[address] = data; } } //************************************************** Ymfm2608::~Ymfm2608() { stopDevice(); } int Ymfm2608::startDevice(int clock, int& rateSsg, uint32_t dramSize) { ymfmIntf_ = std::make_unique(dramSize); ymfm_ = std::make_unique(*ymfmIntf_); // Prescale = 6 // Set FM output rate ymfm_->set_fidelity(ymfm::opn_fidelity::OPN_FIDELITY_MIN); rateSsg = clock / 32; ymfm_->reset(); return clock / 144; // FM synthesis rate is clock / 2 / 72 } void Ymfm2608::stopDevice() { ymfm_.reset(); } void Ymfm2608::resetDevice() { ymfm_->reset(); } void Ymfm2608::writeAddressToPortA(uint8_t address) { ymfm_->write_address(address); } void Ymfm2608::writeAddressToPortB(uint8_t address) { ymfm_->write_address_hi(address); } void Ymfm2608::writeDataToPortA(uint8_t data) { ymfm_->write_data(data); } void Ymfm2608::writeDataToPortB(uint8_t data) { ymfm_->write_data_hi(data); } uint8_t Ymfm2608::readData() { return ymfm_->read_data(); } void Ymfm2608::updateStream(sample** outputs, int nSamples) { sample* bufl = outputs[STEREO_LEFT]; sample* bufr = outputs[STEREO_RIGHT]; ymfm::ym2608::output_data data; for (int i = 0; i < nSamples; ++i) { ymfm_->generate_fm_adpcm(&data); // Raise volume *bufl++ = data.data[0] << 1; *bufr++ = data.data[1] << 1; } } void Ymfm2608::updateSsgStream(sample** outputs, int nSamples) { sample* bufl = outputs[STEREO_LEFT]; sample* bufr = outputs[STEREO_RIGHT]; ymfm::ym2608::output_data data; for (int i = 0; i < nSamples; ++i) { ymfm_->generate_ssg(&data); // Modify volume int32_t s = data.data[2] * 3 / 4; *bufl++ = s; *bufr++ = s; } } } BambooTracker-0.6.5/BambooTracker/chip/ymfm/ymfm_2608.hpp000066400000000000000000000043111476276175200230230ustar00rootroot00000000000000/* * Copyright (C) 2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "../2608_interface.hpp" #include #include #include "ymfm.h" #include "ymfm_opn.h" namespace chip { class Ymfm2608 final : public Ym2608Interface { public: ~Ymfm2608() override; int startDevice(int clock, int& rateSsg, uint32_t dramSize) override; void stopDevice() override; void resetDevice() override; void writeAddressToPortA(uint8_t address) override; void writeAddressToPortB(uint8_t address) override; void writeDataToPortA(uint8_t data) override; void writeDataToPortB(uint8_t data) override; uint8_t readData() override; void updateStream(sample** outputs, int nSamples) override; void updateSsgStream(sample** outputs, int nSamples) override; private: class YmfmInterface final : public ymfm::ymfm_interface { public: YmfmInterface(uint32_t dramSize); uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address) override; void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data) override; private: std::vector dram_; }; std::unique_ptr ymfm_; std::unique_ptr ymfmIntf_; }; } BambooTracker-0.6.5/BambooTracker/chip/ymfm/ymfm_adpcm.cpp000066400000000000000000000530301476276175200235050ustar00rootroot00000000000000// 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); } } BambooTracker-0.6.5/BambooTracker/chip/ymfm/ymfm_adpcm.h000066400000000000000000000326741476276175200231650ustar00rootroot00000000000000// 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 BambooTracker-0.6.5/BambooTracker/chip/ymfm/ymfm_fm.h000066400000000000000000000425031476276175200224730ustar00rootroot00000000000000// 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 #include #include "ymfm.h" #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 BambooTracker-0.6.5/BambooTracker/chip/ymfm/ymfm_fm.ipp000066400000000000000000001634531476276175200230440ustar00rootroot00000000000000// 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); } } } BambooTracker-0.6.5/BambooTracker/chip/ymfm/ymfm_opn.cpp000066400000000000000000002061471476276175200232260ustar00rootroot00000000000000// 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; } #if 0 //********************************************************* // 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(); } #endif //********************************************************* // 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 //------------------------------------------------- /* [BambooTracker] Separate sample generate with FM and SSG */ //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); //} /* [BambooTracker] * We use only prescale = 6, match output rate to FM synthesis rate */ void ym2608::generate_fm_adpcm(output_data *output, uint32_t numsamples) { for (uint32_t samp = 0; samp < numsamples; samp++, output++) { clock_fm_and_adpcm(); output->data[0] = m_last_fm.data[0]; output->data[1] = m_last_fm.data[1]; } } /* [BambooTracker] * We use only prescale = 6 and do not resample SSG */ void ym2608::generate_ssg(output_data *output, uint32_t numsamples) { // TODO // resample the SSG as configured m_ssg_resampler.resample(output, 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: /* [BambooTracker] * We use only prescale = 6, match output rate to FM synthesis rate * and do not resample SSG */ case 6: m_fm_samples_per_output = 1; m_ssg_resampler.configure(1, 1); 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(); } #if 0 //********************************************************* // 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); } } #endif } BambooTracker-0.6.5/BambooTracker/chip/ymfm/ymfm_opn.h000066400000000000000000000702271476276175200226710ustar00rootroot00000000000000// 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);*/ return (!IsOpnA ? chnum : ((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 (!IsOpnA ? (opnum + opnum / 3) : ((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; }; #if 0 // ======================> 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 }; #endif //********************************************************* // 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 /* [BambooTracker] Separate sample generate with FM and SSG */ /*void generate(output_data *output, uint32_t numsamples = 1);*/ void generate_fm_adpcm(output_data *output, uint32_t numsamples = 1); void generate_ssg(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 }; #if 0 // ======================> 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 } #endif // YMFM_OPN_H BambooTracker-0.6.5/BambooTracker/chip/ymfm/ymfm_ssg.cpp000066400000000000000000000203451476276175200232200ustar00rootroot00000000000000// 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); } } BambooTracker-0.6.5/BambooTracker/chip/ymfm/ymfm_ssg.h000066400000000000000000000170431476276175200226660ustar00rootroot00000000000000// 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 BambooTracker-0.6.5/BambooTracker/command/000077500000000000000000000000001476276175200204075ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/command/abstract_command.hpp000066400000000000000000000007261476276175200244260ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include "command_id.hpp" class AbstractCommand { public: AbstractCommand(CommandId id) : id_(id) {} virtual ~AbstractCommand() = default; virtual bool redo() = 0; virtual bool undo() = 0; inline CommandId getID() const noexcept { return id_; } virtual bool mergeWith(const AbstractCommand* other) { (void)other; return false; } private: const CommandId id_; }; BambooTracker-0.6.5/BambooTracker/command/command_id.hpp000066400000000000000000000047011476276175200232140ustar00rootroot00000000000000/* * Copyright (C) 2019-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once enum CommandId : int { // 0x1*: Instrument list AddInstrument = 0x10, RemoveInstrument = 0x11, ChangeInstrumentName = 0x12, CloneInstrument = 0x13, DeepCloneInstrument = 0x14, SwapInstruments = 0x15, // 0x2*, 0x3*: Pattern editor SetKeyOnToStep = 0x20, SetKeyOffToStep = 0x21, EraseStep = 0x22, SetInstrumentInStep = 0x23, EraseInstrumentInStep = 0x24, SetVolumeToStep = 0x25, EraseVolumeInStep = 0x26, SetEffectIDToStep = 0x27, EraseEffectInStep = 0x28, SetEffectValueToStep = 0x29, EraseEffectValueInStep = 0x2a, InsertStep = 0x2b, DeletePreviousStep = 0x2c, PasteCopiedDataToPattern = 0x2d, EraseCellsInPattern = 0x2e, PasteMixCopiedDataToPattern = 0x2f, TransposeNoteInPattern = 0x30, ChangeValuesInPattern = 0x31, ExpandPattern = 0x34, ShrinkPattern = 0x35, SetEchoBufferAccess = 0x36, InterpolatePattern = 0x37, ReversePattern = 0x38, ReplaceInstrumentInPattern = 0x39, PasteOverwriteCopiedDataToPattern = 0x3a, PasteInsertCopiedDataToPattern = 0x3b, SetKeyCutToStep = 0x3c, // 0x4*: Order list SetPatternToOrder = 0x40, InsertOrderBelow = 0x41, DeleteOrder = 0x42, PasteCopiedDataToOrder = 0x43, DuplicateOrder = 0x44, MoveOrder = 0x45, ClonePatterns = 0x46, CloneOrder = 0x47 }; BambooTracker-0.6.5/BambooTracker/command/command_manager.cpp000066400000000000000000000022051476276175200242220ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "command_manager.hpp" #include bool CommandManager::invoke(CommandIPtr command) { redoStack_.clear(); if (!undoStack_.empty() && undoStack_.back()->mergeWith(command.get())) { return true; } redoStack_.push_back(std::move(command)); return redo(); } bool CommandManager::undo() { if (undoStack_.empty()) return true; CommandIPtr command = std::move(undoStack_.back()); if (!command) return false; if (command->undo()) { // Push and pop history stacks. undoStack_.pop_back(); redoStack_.push_back(std::move(command)); return true; } else { // Rollback. command->redo(); return false; } } bool CommandManager::redo() { if (redoStack_.empty()) return true; CommandIPtr command = std::move(redoStack_.back()); if (!command) return false; if (command->redo()) { // Push and pop history stacks. redoStack_.pop_back(); undoStack_.push_back(std::move(command)); return true; } else { // Rollback. command->undo(); return false; } } void CommandManager::clear() { redoStack_.clear(); undoStack_.clear(); } BambooTracker-0.6.5/BambooTracker/command/command_manager.hpp000066400000000000000000000006041476276175200242300ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "abstract_command.hpp" class CommandManager { public: using CommandIPtr = std::unique_ptr; bool invoke(CommandIPtr command); bool undo(); bool redo(); void clear(); private: std::deque undoStack_, redoStack_; }; BambooTracker-0.6.5/BambooTracker/command/commands.hpp000066400000000000000000000066171476276175200227330ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once /********** Instrument edit **********/ #include "./instrument/add_instrument_command.hpp" #include "./instrument/remove_instrument_command.hpp" #include "./instrument/change_instrument_name_command.hpp" #include "./instrument/clone_instrument_command.hpp" #include "./instrument/deep_clone_instrument_command.hpp" #include "./instrument/swap_instruments_command.hpp" /********** Pattern edit **********/ #include "./pattern/set_key_on_to_step_command.hpp" #include "./pattern/set_key_off_to_step_command.hpp" #include "./pattern/erase_step_command.hpp" #include "./pattern/set_instrument_to_step_command.hpp" #include "./pattern/erase_instrument_in_step_command.hpp" #include "./pattern/set_volume_to_step_command.hpp" #include "./pattern/erase_volume_in_step_command.hpp" #include "./pattern/set_effect_id_to_step_command.hpp" #include "./pattern/erase_effect_in_step_command.hpp" #include "./pattern/set_effect_value_to_step_command.hpp" #include "./pattern/erase_effect_value_in_step_command.hpp" #include "./pattern/insert_step_command.hpp" #include "./pattern/delete_previous_step_command.hpp" #include "./pattern/paste_copied_data_to_pattern_command.hpp" #include "./pattern/erase_cells_in_pattern_command.hpp" #include "./pattern/paste_mix_copied_data_to_pattern_command.hpp" #include "./pattern/transpose_note_in_pattern_command.hpp" #include "./pattern/expand_pattern_command.hpp" #include "./pattern/shrink_pattern_command.hpp" #include "./pattern/set_echo_buffer_access_command.hpp" #include "./pattern/interpolate_pattern_command.hpp" #include "./pattern/reverse_pattern_command.hpp" #include "./pattern/replace_instrument_in_pattern_command.hpp" #include "./pattern/paste_overwrite_copied_data_to_pattern_command.hpp" #include "./pattern/change_values_in_pattern_command.hpp" #include "./pattern/paste_insert_copied_data_to_pattern_command.hpp" #include "./pattern/set_key_cut_to_step_command.hpp" /********** Order edit **********/ #include "./order/set_pattern_to_order_command.hpp" #include "./order/insert_order_below_command.hpp" #include "./order/delete_order_command.hpp" #include "./order/paste_copied_data_to_order_command.hpp" #include "./order/duplicate_order_command.hpp" #include "./order/move_order_command.hpp" #include "./order/clone_patterns_command.hpp" #include "./order/clone_order_command.hpp" BambooTracker-0.6.5/BambooTracker/command/instrument/000077500000000000000000000000001476276175200226175ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/command/instrument/add_instrument_command.cpp000066400000000000000000000016561476276175200300510ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "add_instrument_command.hpp" #include AddInstrumentCommand::AddInstrumentCommand(std::weak_ptr manager, int num, InstrumentType type, const std::string& name) : AbstractCommand(CommandId::AddInstrument), manager_(manager), num_(num), type_(type), name_(name) { } AddInstrumentCommand::AddInstrumentCommand(std::weak_ptr manager, std::unique_ptr inst) : AbstractCommand(CommandId::AddInstrument), manager_(manager), num_(inst->getNumber()), inst_(std::move(inst)) { } bool AddInstrumentCommand::redo() { if (inst_) manager_.lock()->addInstrument(inst_->clone()); else manager_.lock()->addInstrument(num_, type_, name_); return true; } bool AddInstrumentCommand::undo() { manager_.lock()->removeInstrument(num_); return true; } BambooTracker-0.6.5/BambooTracker/command/instrument/add_instrument_command.hpp000066400000000000000000000013431476276175200300470ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "instrument.hpp" #include "instruments_manager.hpp" class AddInstrumentCommand final : public AbstractCommand { public: AddInstrumentCommand(std::weak_ptr manager, int num, InstrumentType type, const std::string& name); AddInstrumentCommand(std::weak_ptr manager, std::unique_ptr inst); bool redo() override; bool undo() override; private: std::weak_ptr manager_; int num_; InstrumentType type_; std::string name_; std::unique_ptr inst_; }; BambooTracker-0.6.5/BambooTracker/command/instrument/change_instrument_name_command.cpp000066400000000000000000000012521476276175200315360ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "change_instrument_name_command.hpp" ChangeInstrumentNameCommand::ChangeInstrumentNameCommand(std::weak_ptr manager, int num, const std::string& name) : AbstractCommand(CommandId::ChangeInstrumentName), manager_(manager), instNum_(num), newName_(name) { oldName_ = manager_.lock()->getInstrumentName(instNum_); } bool ChangeInstrumentNameCommand::redo() { manager_.lock()->setInstrumentName(instNum_, newName_); return true; } bool ChangeInstrumentNameCommand::undo() { manager_.lock()->setInstrumentName(instNum_, oldName_); return true; } BambooTracker-0.6.5/BambooTracker/command/instrument/change_instrument_name_command.hpp000066400000000000000000000010361476276175200315430ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "instruments_manager.hpp" class ChangeInstrumentNameCommand final : public AbstractCommand { public: ChangeInstrumentNameCommand(std::weak_ptr manager, int num, const std::string& name); bool redo() override; bool undo() override; private: std::weak_ptr manager_; int instNum_; std::string oldName_, newName_; }; BambooTracker-0.6.5/BambooTracker/command/instrument/clone_instrument_command.cpp000066400000000000000000000011151476276175200304070ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "clone_instrument_command.hpp" cloneInstrumentCommand::cloneInstrumentCommand(std::weak_ptr manager, int num, int refNum) : AbstractCommand(CommandId::CloneInstrument), manager_(manager), cloneInstNum_(num), refInstNum_(refNum) { } bool cloneInstrumentCommand::redo() { manager_.lock()->cloneInstrument(cloneInstNum_, refInstNum_); return true; } bool cloneInstrumentCommand::undo() { manager_.lock()->removeInstrument(cloneInstNum_); return true; } BambooTracker-0.6.5/BambooTracker/command/instrument/clone_instrument_command.hpp000066400000000000000000000007361476276175200304240ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include "../abstract_command.hpp" #include "instruments_manager.hpp" class cloneInstrumentCommand final : public AbstractCommand { public: cloneInstrumentCommand(std::weak_ptr manager, int num, int refNum); bool redo() override; bool undo() override; private: std::weak_ptr manager_; int cloneInstNum_, refInstNum_; }; BambooTracker-0.6.5/BambooTracker/command/instrument/deep_clone_instrument_command.cpp000066400000000000000000000011541476276175200314070ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "deep_clone_instrument_command.hpp" DeepCloneInstrumentCommand::DeepCloneInstrumentCommand(std::weak_ptr manager, int num, int refNum) : AbstractCommand(CommandId::DeepCloneInstrument), manager_(manager), cloneInstNum_(num), refInstNum_(refNum) { } bool DeepCloneInstrumentCommand::redo() { manager_.lock()->deepCloneInstrument(cloneInstNum_, refInstNum_); return true; } bool DeepCloneInstrumentCommand::undo() { manager_.lock()->removeInstrument(cloneInstNum_); return true; } BambooTracker-0.6.5/BambooTracker/command/instrument/deep_clone_instrument_command.hpp000066400000000000000000000007461476276175200314220ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include "../abstract_command.hpp" #include "instruments_manager.hpp" class DeepCloneInstrumentCommand final : public AbstractCommand { public: DeepCloneInstrumentCommand(std::weak_ptr manager, int num, int refNum); bool redo() override; bool undo() override; private: std::weak_ptr manager_; int cloneInstNum_, refInstNum_; }; BambooTracker-0.6.5/BambooTracker/command/instrument/remove_instrument_command.cpp000066400000000000000000000010601476276175200306030ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "remove_instrument_command.hpp" #include RemoveInstrumentCommand::RemoveInstrumentCommand(std::weak_ptr manager, int number) : AbstractCommand(CommandId::RemoveInstrument), manager_(manager), number_(number) { } bool RemoveInstrumentCommand::redo() { inst_ = manager_.lock()->removeInstrument(number_); return true; } bool RemoveInstrumentCommand::undo() { manager_.lock()->addInstrument(inst_.release()); return true; } BambooTracker-0.6.5/BambooTracker/command/instrument/remove_instrument_command.hpp000066400000000000000000000010121476276175200306050ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include "../abstract_command.hpp" #include "instruments_manager.hpp" #include "instrument.hpp" class RemoveInstrumentCommand final : public AbstractCommand { public: RemoveInstrumentCommand(std::weak_ptr manager, int number); bool redo() override; bool undo() override; private: std::weak_ptr manager_; int number_; std::unique_ptr inst_; }; BambooTracker-0.6.5/BambooTracker/command/instrument/swap_instruments_command.cpp000066400000000000000000000027751476276175200304610ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2020 Rerrah * SPDX-License-Identifier: MIT */ #include "swap_instruments_command.hpp" SwapInstrumentsCommand::SwapInstrumentsCommand(std::weak_ptr manager, std::weak_ptr mod, int inst1, int inst2, int song, bool patternChange) : AbstractCommand(CommandId::SwapInstruments), manager_(manager), mod_(mod), inst1Num_(inst1), inst2Num_(inst2), songNum_(song), patternChange_(patternChange) { } bool SwapInstrumentsCommand::redo() { manager_.lock()->swapInstruments(inst1Num_, inst2Num_); if (patternChange_) swapInstrumentsInPatterns(); return true; } bool SwapInstrumentsCommand::undo() { manager_.lock()->swapInstruments(inst1Num_, inst2Num_); if (patternChange_) swapInstrumentsInPatterns(); return true; } void SwapInstrumentsCommand::swapInstrumentsInPatterns() { // OPTIMIZE: It is too slow! for (size_t n = 0; n < mod_.lock()->getSongCount(); ++n) { Song& song = mod_.lock()->getSong(static_cast(n)); for (const auto& attrib : song.getStyle().trackAttribs) { Track& track = song.getTrack(attrib.number); for (int i = 0; i < 256; ++i) { // Used track size Pattern& pat = track.getPattern(i); for (size_t j = 0; j < pat.getSize(); ++j) { Step& step = pat.getStep(static_cast(j)); if (step.getInstrumentNumber() == inst1Num_) step.setInstrumentNumber(inst2Num_); else if (step.getInstrumentNumber() == inst2Num_) step.setInstrumentNumber(inst1Num_); } } } } } BambooTracker-0.6.5/BambooTracker/command/instrument/swap_instruments_command.hpp000066400000000000000000000012271476276175200304550ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2020 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include "../abstract_command.hpp" #include "instruments_manager.hpp" #include "module.hpp" class SwapInstrumentsCommand final : public AbstractCommand { public: SwapInstrumentsCommand(std::weak_ptr manager, std::weak_ptr mod, int inst1, int inst2, int song, bool patternChange); bool redo() override; bool undo() override; private: std::weak_ptr manager_; std::weak_ptr mod_; int inst1Num_, inst2Num_; int songNum_; bool patternChange_; void swapInstrumentsInPatterns(); }; BambooTracker-0.6.5/BambooTracker/command/order/000077500000000000000000000000001476276175200215225ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/command/order/clone_order_command.cpp000066400000000000000000000020241476276175200262150ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "clone_order_command.hpp" CloneOrderCommand::CloneOrderCommand(std::weak_ptr mod, int songNum, int orderNum) : AbstractCommand(CommandId::CloneOrder), mod_(mod), song_(songNum), order_(orderNum) { } bool CloneOrderCommand::redo() { auto& sng = mod_.lock()->getSong(song_); sng.insertOrderBelow(order_); for (auto& t : sng.getTrackAttributes()) { auto& track = sng.getTrack(t.number); // Set previous pattern to avoid leaving unused pattern track.registerPatternToOrder(order_ + 1, track.getPatternFromOrderNumber(order_).getNumber()); track.registerPatternToOrder(order_ + 1, track.clonePattern(track.getOrderInfo(order_).patten)); } return true; } bool CloneOrderCommand::undo() { auto& sng = mod_.lock()->getSong(song_); for (auto& t : sng.getTrackAttributes()) { auto& p = sng.getTrack(t.number).getPatternFromOrderNumber(order_ + 1); if (p.getUsedCount() == 1) p.clear(); } sng.deleteOrder(order_ + 1); return true; } BambooTracker-0.6.5/BambooTracker/command/order/clone_order_command.hpp000066400000000000000000000007241476276175200262270ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "module.hpp" class CloneOrderCommand final : public AbstractCommand { public: CloneOrderCommand(std::weak_ptr mod, int songNum, int orderNum); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, order_; std::vector prevOdr_; }; BambooTracker-0.6.5/BambooTracker/command/order/clone_patterns_command.cpp000066400000000000000000000027051476276175200267500ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "clone_patterns_command.hpp" ClonePatternsCommand::ClonePatternsCommand(std::weak_ptr mod, int songNum, int beginOrder, int beginTrack, int endOrder, int endTrack) : AbstractCommand(CommandId::ClonePatterns), mod_(mod), song_(songNum), bOrder_(beginOrder), bTrack_(beginTrack), eOrder_(endOrder), eTrack_(endTrack) { for (int o = beginOrder; o <= endOrder; ++o) { prevOdrs_.emplace_back(); for (int t = beginTrack; t <= endTrack; ++t) { prevOdrs_.at(static_cast(o - beginOrder)).push_back( mod_.lock()->getSong(songNum).getTrack(t).getOrderInfo(o)); } } } bool ClonePatternsCommand::redo() { auto& sng = mod_.lock()->getSong(song_); for (int o = bOrder_; o <= eOrder_; ++o) { for (int t = bTrack_; t <= eTrack_; ++t) { auto& track = sng.getTrack(t); track.registerPatternToOrder(o, track.clonePattern(track.getOrderInfo(o).patten)); } } return true; } bool ClonePatternsCommand::undo() { auto& sng = mod_.lock()->getSong(song_); for (int o = bOrder_; o <= eOrder_; ++o) { for (int t = bTrack_; t <= eTrack_; ++t) { auto& track = sng.getTrack(t); auto& p = track.getPatternFromOrderNumber(o); if (p.getUsedCount() == 1) p.clear(); track.registerPatternToOrder( o, prevOdrs_.at(static_cast(o - bOrder_)).at(static_cast(t - bTrack_)).patten); } } return true; } BambooTracker-0.6.5/BambooTracker/command/order/clone_patterns_command.hpp000066400000000000000000000010711476276175200267500ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "module.hpp" class ClonePatternsCommand final : public AbstractCommand { public: ClonePatternsCommand(std::weak_ptr mod, int songNum, int beginOrder, int beginTrack, int endOrder, int endTrack); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, bOrder_, bTrack_, eOrder_, eTrack_; std::vector> prevOdrs_; }; BambooTracker-0.6.5/BambooTracker/command/order/delete_order_command.cpp000066400000000000000000000013141476276175200263600ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "delete_order_command.hpp" DeleteOrderCommand::DeleteOrderCommand(std::weak_ptr mod, int songNum, int orderNum) : AbstractCommand(CommandId::DeleteOrder), mod_(mod), song_(songNum), order_(orderNum) { prevOdr_ = mod_.lock()->getSong(songNum).getOrderData(orderNum); } bool DeleteOrderCommand::redo() { mod_.lock()->getSong(song_).deleteOrder(order_); return true; } bool DeleteOrderCommand::undo() { auto& sng = mod_.lock()->getSong(song_); sng.insertOrderBelow(order_ - 1); for (const auto& t : prevOdr_) { sng.getTrack(t.trackAttribute.number).registerPatternToOrder(t.order, t.patten); } return true; } BambooTracker-0.6.5/BambooTracker/command/order/delete_order_command.hpp000066400000000000000000000007261476276175200263730ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "module.hpp" class DeleteOrderCommand final : public AbstractCommand { public: DeleteOrderCommand(std::weak_ptr mod, int songNum, int orderNum); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, order_; std::vector prevOdr_; }; BambooTracker-0.6.5/BambooTracker/command/order/duplicate_order_command.cpp000066400000000000000000000013121476276175200270660ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "duplicate_order_command.hpp" DuplicateOrderCommand::DuplicateOrderCommand(std::weak_ptr mod, int songNum, int orderNum) : AbstractCommand(CommandId::DuplicateOrder), mod_(mod), song_(songNum), order_(orderNum) { } bool DuplicateOrderCommand::redo() { auto& sng = mod_.lock()->getSong(song_); sng.insertOrderBelow(order_); for (auto& t : sng.getTrackAttributes()) { auto& track = sng.getTrack(t.number); track.registerPatternToOrder(order_ + 1, track.getOrderInfo(order_).patten); } return true; } bool DuplicateOrderCommand::undo() { mod_.lock()->getSong(song_).deleteOrder(order_ + 1); return true; } BambooTracker-0.6.5/BambooTracker/command/order/duplicate_order_command.hpp000066400000000000000000000006421476276175200271000ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include "../abstract_command.hpp" #include "module.hpp" class DuplicateOrderCommand : public AbstractCommand { public: DuplicateOrderCommand(std::weak_ptr mod, int songNum, int orderNum); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, order_; }; BambooTracker-0.6.5/BambooTracker/command/order/insert_order_below_command.cpp000066400000000000000000000010371476276175200276140ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "insert_order_below_command.hpp" InsertOrderBelowCommand::InsertOrderBelowCommand(std::weak_ptr mod, int songNum, int orderNum) : AbstractCommand(CommandId::InsertOrderBelow), mod_(mod), song_(songNum), order_(orderNum) { } bool InsertOrderBelowCommand::redo() { mod_.lock()->getSong(song_).insertOrderBelow(order_); return true; } bool InsertOrderBelowCommand::undo() { mod_.lock()->getSong(song_).deleteOrder(order_ + 1); return true; } BambooTracker-0.6.5/BambooTracker/command/order/insert_order_below_command.hpp000066400000000000000000000006541476276175200276250ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include "../abstract_command.hpp" #include "module.hpp" class InsertOrderBelowCommand final : public AbstractCommand { public: InsertOrderBelowCommand(std::weak_ptr mod, int songNum, int orderNum); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, order_; }; BambooTracker-0.6.5/BambooTracker/command/order/move_order_command.cpp000066400000000000000000000011261476276175200260650ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "move_order_command.hpp" MoveOrderCommand::MoveOrderCommand(std::weak_ptr mod, int songNum, int orderNum, bool isUp) : AbstractCommand(CommandId::MoveOrder), mod_(mod), song_(songNum), order_(orderNum), isUp_(isUp) { } bool MoveOrderCommand::redo() { swap(); return true; } bool MoveOrderCommand::undo() { swap(); return true; } void MoveOrderCommand::swap() { auto& sng = mod_.lock()->getSong(song_); if (isUp_) sng.swapOrder(order_ - 1, order_); else sng.swapOrder(order_, order_ + 1); } BambooTracker-0.6.5/BambooTracker/command/order/move_order_command.hpp000066400000000000000000000007051476276175200260740ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include "../abstract_command.hpp" #include "module.hpp" class MoveOrderCommand final : public AbstractCommand { public: MoveOrderCommand(std::weak_ptr mod, int songNum, int orderNum, bool isUp); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, order_; bool isUp_; void swap(); }; BambooTracker-0.6.5/BambooTracker/command/order/paste_copied_data_to_order_command.cpp000066400000000000000000000026531476276175200312570ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "paste_copied_data_to_order_command.hpp" #include "track.hpp" PasteCopiedDataToOrderCommand::PasteCopiedDataToOrderCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginOrder, const Vector2d& cells) : AbstractCommand(CommandId::PasteCopiedDataToOrder), mod_(mod), song_(songNum), track_(beginTrack), order_(beginOrder), cells_(cells), prevCells_(cells.shape()) { const auto& sng = mod.lock()->getSong(songNum); for (std::size_t i = 0; i < cells.rowSize(); ++i) { const std::vector odrs = sng.getOrderData(beginOrder + static_cast(i)); for (std::size_t j = 0; j < cells.columnSize(); ++j) { prevCells_[i][j] = odrs.at(static_cast(beginTrack) + j).patten; } } } bool PasteCopiedDataToOrderCommand::redo() { try { setCells(cells_); return true; } catch (...) { return false; } } bool PasteCopiedDataToOrderCommand::undo() { try { setCells(prevCells_); return true; } catch (...) { return false; } } void PasteCopiedDataToOrderCommand::setCells(const Vector2d& cells) { auto& sng = mod_.lock()->getSong(song_); for (std::size_t i = 0; i < cells.rowSize(); ++i) { for (std::size_t j = 0; j < cells.columnSize(); ++j) { sng.getTrack(track_ + static_cast(j)) .registerPatternToOrder(order_ + static_cast(i), cells[i][j]); } } } BambooTracker-0.6.5/BambooTracker/command/order/paste_copied_data_to_order_command.hpp000066400000000000000000000011601476276175200312540ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "module.hpp" #include "vector_2d.hpp" class PasteCopiedDataToOrderCommand final : public AbstractCommand { public: PasteCopiedDataToOrderCommand( std::weak_ptr mod, int songNum, int beginTrack, int beginOrder, const Vector2d& cells); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, track_, order_; Vector2d cells_, prevCells_; void setCells(const Vector2d& cells); }; BambooTracker-0.6.5/BambooTracker/command/order/set_pattern_to_order_command.cpp000066400000000000000000000025711476276175200301560ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "set_pattern_to_order_command.hpp" SetPatternToOrderCommand::SetPatternToOrderCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int patternNum, bool secondEntry) : AbstractCommand(CommandId::SetPatternToOrder), mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), pattern_(patternNum), prevPattern_(mod_.lock()->getSong(songNum).getTrack(trackNum) .getPatternFromOrderNumber(orderNum).getNumber()), isSecondEntry_(secondEntry) { } bool SetPatternToOrderCommand::redo() { mod_.lock()->getSong(song_).getTrack(track_).registerPatternToOrder(order_, pattern_); return true; } bool SetPatternToOrderCommand::undo() { mod_.lock()->getSong(song_).getTrack(track_).registerPatternToOrder(order_, prevPattern_); isSecondEntry_ = true; // Forced complete return true; } bool SetPatternToOrderCommand::mergeWith(const AbstractCommand* other) { if (other->getID() == getID() && !isSecondEntry_) { auto com = dynamic_cast(other); if (com->song_ == song_ && com->track_ == track_ && com->order_ == order_ && com->isSecondEntry_) { pattern_ = (pattern_ << 4) + com->pattern_; redo(); isSecondEntry_ = true; return true; } } isSecondEntry_ = true; return false; } BambooTracker-0.6.5/BambooTracker/command/order/set_pattern_to_order_command.hpp000066400000000000000000000011521476276175200301550ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include "../abstract_command.hpp" #include "module.hpp" class SetPatternToOrderCommand final : public AbstractCommand { public: SetPatternToOrderCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int patternNum, bool secondEntry); bool redo() override; bool undo() override; bool mergeWith(const AbstractCommand* other) override; private: std::weak_ptr mod_; const int song_, track_, order_; int pattern_; const int prevPattern_; bool isSecondEntry_; }; BambooTracker-0.6.5/BambooTracker/command/pattern/000077500000000000000000000000001476276175200220645ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/command/pattern/change_values_in_pattern_command.cpp000066400000000000000000000065351476276175200313260ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2020 Rerrah * SPDX-License-Identifier: MIT */ #include "change_values_in_pattern_command.hpp" #include "pattern_command_utils.hpp" #include "bamboo_tracker_defs.hpp" #include "utils.hpp" ChangeValuesInPatternCommand::ChangeValuesInPatternCommand( std::weak_ptr mod, int songNum, int beginTrack, int beginColumn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep, int value, bool isFMReversed) : AbstractCommand(CommandId::ChangeValuesInPattern), mod_(mod), song_(songNum), bTrack_(beginTrack), bCol_(beginColumn), order_(beginOrder), bStep_(beginStep), eTrack_(endTrack), eCol_(endColumn), eStep_(endStep), diff_(value), fmReverse_(isFMReversed) { auto& sng = mod.lock()->getSong(songNum); for (int step = beginStep; step <= endStep; ++step) { int track = beginTrack; int col = beginColumn; std::vector vals; while (true) { Step& st = command_utils::getStep(sng, track, beginOrder, step); switch (col) { case 1: if (st.hasInstrument()) vals.push_back(st.getInstrumentNumber()); break; case 2: if (st.hasVolume()) vals.push_back(st.getVolume()); break; default: { if (col) { int ec = col - 3; int ei = ec / 2; if (ec % 2 && st.hasEffectValue(ei)) vals.push_back(st.getEffectValue(ei)); } break; } } if (track == endTrack && col == endColumn) break; track += (++col / Step::N_COLUMN); col %= Step::N_COLUMN; } prevVals_.push_back(vals); } } bool ChangeValuesInPatternCommand::redo() { auto& sng = mod_.lock()->getSong(song_); auto it = prevVals_.begin(); for (int step = bStep_; step <= eStep_; ++step, ++it) { int track = bTrack_; int col = bCol_; auto valit = it->begin(); while (true) { Track& tr = sng.getTrack(track); Step& st = tr.getPatternFromOrderNumber(order_).getStep(step); switch (col) { case 1: if (st.hasInstrument()) st.setInstrumentNumber(utils::clamp(diff_ + *valit++, 0, 127)); break; case 2: if (st.hasVolume()) { int d = (tr.getAttribute().source == SoundSource::FM && fmReverse_) ? -diff_ : diff_; st.setVolume(utils::clamp(d + *valit++, 0, 255)); } break; default: { if (col) { int ec = col - 3; int ei = ec / 2; if (ec % 2 && st.hasEffectValue(ei)) st.setEffectValue(ei, utils::clamp(diff_ + *valit++, 0, 255)); } break; } } if (track == eTrack_ && col == eCol_) break; track += (++col / Step::N_COLUMN); col %= Step::N_COLUMN; } } return true; } bool ChangeValuesInPatternCommand::undo() { auto& sng = mod_.lock()->getSong(song_); auto it = prevVals_.begin(); for (int step = bStep_; step <= eStep_; ++step, ++it) { int track = bTrack_; int col = bCol_; auto valit = it->begin(); while (true) { Step& st = command_utils::getStep(sng, track, order_, step); switch (col) { case 1: if (st.hasInstrument()) st.setInstrumentNumber(*valit++); break; case 2: if (st.hasVolume()) st.setVolume(*valit++); break; default: { if (col) { int ec = col - 3; int ei = ec / 2; if (ec % 2 && st.hasEffectValue(ei)) st.setEffectValue(ei, *valit++); } break; } } if (track == eTrack_ && col == eCol_) break; track += (++col / Step::N_COLUMN); col %= Step::N_COLUMN; } } return true; } BambooTracker-0.6.5/BambooTracker/command/pattern/change_values_in_pattern_command.hpp000066400000000000000000000013261476276175200313240ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2020 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "module.hpp" class ChangeValuesInPatternCommand final : public AbstractCommand { public: ChangeValuesInPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginColumn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep, int value, bool isFMReversed); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_; int bTrack_, bCol_, order_, bStep_; int eTrack_, eCol_, eStep_; int diff_; bool fmReverse_; std::vector> prevVals_; }; BambooTracker-0.6.5/BambooTracker/command/pattern/delete_previous_step_command.cpp000066400000000000000000000023201476276175200305140ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "delete_previous_step_command.hpp" #include "pattern_command_utils.hpp" DeletePreviousStepCommand::DeletePreviousStepCommand( std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum) : AbstractCommand(CommandId::DeletePreviousStep), mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum) { Step& st = command_utils::getStep(mod, songNum, trackNum, orderNum, stepNum - 1); prevNote_ = st.getNoteNumber(); prevInst_ = st.getInstrumentNumber(); prevVol_ = st.getVolume(); for (int i = 0; i < Step::N_EFFECT; ++i) { prevEff_[i] = st.getEffect(i); } } bool DeletePreviousStepCommand::redo() { command_utils::getPattern(mod_, song_, track_, order_).deletePreviousStep(step_); return true; } bool DeletePreviousStepCommand::undo() { auto& pt = command_utils::getPattern(mod_, song_, track_, order_); pt.insertStep(step_ - 1); // Insert previous step auto& st = pt.getStep(step_ - 1); st.setNoteNumber(prevNote_); st.setInstrumentNumber(prevInst_); st.setVolume(prevVol_); for (int i = 0; i < Step::N_EFFECT; ++i) { st.setEffect(i, prevEff_[i]); } return true; } BambooTracker-0.6.5/BambooTracker/command/pattern/delete_previous_step_command.hpp000066400000000000000000000011071476276175200305230ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "module.hpp" class DeletePreviousStepCommand final : public AbstractCommand { public: DeletePreviousStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, track_, order_, step_; int prevNote_, prevInst_, prevVol_; Step::PlainEffect prevEff_[Step::N_EFFECT]; }; BambooTracker-0.6.5/BambooTracker/command/pattern/erase_cells_in_pattern_command.cpp000066400000000000000000000040021476276175200307660ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "erase_cells_in_pattern_command.hpp" #include "pattern_command_utils.hpp" EraseCellsInPatternCommand::EraseCellsInPatternCommand( std::weak_ptr mod, int songNum, int beginTrack, int beginColumn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep) : AbstractCommand(CommandId::EraseCellsInPattern), mod_(mod), song_(songNum), bTrack_(beginTrack), bCol_(beginColumn), order_(beginOrder), bStep_(beginStep) { auto& song = mod.lock()->getSong(songNum); std::size_t h = static_cast(endStep - beginStep + 1); std::size_t w = command_utils::calculateColumnSize(beginTrack, beginColumn, endTrack, endColumn); prevCells_ = command_utils::getPreviousCells(song, w, h, beginTrack, beginColumn, beginOrder, beginStep); } bool EraseCellsInPatternCommand::redo() { auto& sng = mod_.lock()->getSong(song_); int stepIndex = bStep_; for (std::size_t i = 0; i < prevCells_.rowSize(); ++i) { int trackIndex = bTrack_; int columnIndex = bCol_; for (std::size_t j = 0; j < prevCells_.columnSize(); ++j) { Step& st = command_utils::getStep(sng, trackIndex, order_, stepIndex); switch (columnIndex) { case 0: st.clearNoteNumber(); break; case 1: st.clearInstrumentNumber(); break; case 2: st.clearVolume(); break; default: { int effectColumnIndex = columnIndex - 3; int effectNumber = effectColumnIndex / 2; if (effectColumnIndex % 2) { // Effect value column. st.clearEffectValue(effectNumber); } else { // Effect ID column. st.clearEffectId(effectNumber); } break; } } trackIndex += (++columnIndex / Step::N_COLUMN); columnIndex %= Step::N_COLUMN; } ++stepIndex; } return true; } bool EraseCellsInPatternCommand::undo() { try { command_utils::restorePattern(mod_.lock()->getSong(song_), prevCells_, bTrack_, bCol_, order_, bStep_); return true; } catch (...) { return false; } } BambooTracker-0.6.5/BambooTracker/command/pattern/erase_cells_in_pattern_command.hpp000066400000000000000000000012101476276175200307710ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "module.hpp" #include "vector_2d.hpp" class EraseCellsInPatternCommand final : public AbstractCommand { public: EraseCellsInPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginColumn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, bTrack_, bCol_, order_, bStep_; Vector2d prevCells_; }; BambooTracker-0.6.5/BambooTracker/command/pattern/erase_effect_in_step_command.cpp000066400000000000000000000014741476276175200304300ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "erase_effect_in_step_command.hpp" #include "pattern_command_utils.hpp" EraseEffectInStepCommand::EraseEffectInStepCommand( std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int n) : AbstractCommand(CommandId::EraseEffectInStep), mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum), n_(n) { prevEff_ = command_utils::getStep(mod, songNum, trackNum, orderNum, stepNum).getEffect(n); } bool EraseEffectInStepCommand::redo() { command_utils::getStep(mod_, song_, track_, order_, step_).clearEffect(n_); return true; } bool EraseEffectInStepCommand::undo() { command_utils::getStep(mod_, song_, track_, order_, step_).setEffect(n_, prevEff_); return true; } BambooTracker-0.6.5/BambooTracker/command/pattern/erase_effect_in_step_command.hpp000066400000000000000000000010321476276175200304230ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "module.hpp" class EraseEffectInStepCommand final : public AbstractCommand { public: EraseEffectInStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int n); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, track_, order_, step_, n_; Step::PlainEffect prevEff_; }; BambooTracker-0.6.5/BambooTracker/command/pattern/erase_effect_value_in_step_command.cpp000066400000000000000000000015521476276175200316210ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "erase_effect_value_in_step_command.hpp" #include "pattern_command_utils.hpp" EraseEffectValueInStepCommand::EraseEffectValueInStepCommand( std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int n) : AbstractCommand(CommandId::EraseEffectValueInStep), mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum), n_(n) { prevVal_ = command_utils::getStep(mod, songNum, trackNum, orderNum, stepNum).getEffectValue(n); } bool EraseEffectValueInStepCommand::redo() { command_utils::getStep(mod_, song_, track_, order_, step_).clearEffectValue(n_); return true; } bool EraseEffectValueInStepCommand::undo() { command_utils::getStep(mod_, song_, track_, order_, step_).setEffectValue(n_, prevVal_); return true; } BambooTracker-0.6.5/BambooTracker/command/pattern/erase_effect_value_in_step_command.hpp000066400000000000000000000010061476276175200316200ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include "../abstract_command.hpp" #include "module.hpp" class EraseEffectValueInStepCommand final : public AbstractCommand { public: EraseEffectValueInStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int n); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, track_, order_, step_, n_; int prevVal_; }; BambooTracker-0.6.5/BambooTracker/command/pattern/erase_instrument_in_step_command.cpp000066400000000000000000000015411476276175200313770ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "erase_instrument_in_step_command.hpp" #include "pattern_command_utils.hpp" EraseInstrumentInStepCommand::EraseInstrumentInStepCommand( std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum) : AbstractCommand(CommandId::EraseInstrumentInStep), mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum) { prevInst_ = command_utils::getStep(mod, songNum, trackNum, orderNum, stepNum) .getInstrumentNumber(); } bool EraseInstrumentInStepCommand::redo() { command_utils::getStep(mod_, song_, track_, order_, step_).clearInstrumentNumber(); return true; } bool EraseInstrumentInStepCommand::undo() { command_utils::getStep(mod_, song_, track_, order_, step_).setInstrumentNumber(prevInst_); return true; } BambooTracker-0.6.5/BambooTracker/command/pattern/erase_instrument_in_step_command.hpp000066400000000000000000000007711476276175200314100ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include "../abstract_command.hpp" #include "module.hpp" class EraseInstrumentInStepCommand final : public AbstractCommand { public: EraseInstrumentInStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, track_, order_, step_; int prevInst_; }; BambooTracker-0.6.5/BambooTracker/command/pattern/erase_step_command.cpp000066400000000000000000000020651476276175200264230ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "erase_step_command.hpp" #include "pattern_command_utils.hpp" EraseStepCommand::EraseStepCommand( std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum) : AbstractCommand(CommandId::EraseStep), mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum) { Step& st = command_utils::getStep(mod, songNum, trackNum, orderNum, stepNum); prevNote_ = st.getNoteNumber(); prevInst_ = st.getInstrumentNumber(); prevVol_ = st.getVolume(); for (int i = 0; i < Step::N_EFFECT; ++i) { prevEff_[i] = st.getEffect(i); } } bool EraseStepCommand::redo() { command_utils::getStep(mod_, song_, track_, order_, step_).clear(); return true; } bool EraseStepCommand::undo() { Step& st = command_utils::getStep(mod_, song_, track_, order_, step_); st.setNoteNumber(prevNote_); st.setInstrumentNumber(prevInst_); st.setVolume(prevVol_); for (int i = 0; i < Step::N_EFFECT; ++i) { st.setEffect(i, prevEff_[i]); } return true; } BambooTracker-0.6.5/BambooTracker/command/pattern/erase_step_command.hpp000066400000000000000000000010541476276175200264250ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "module.hpp" class EraseStepCommand final : public AbstractCommand { public: EraseStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, track_, order_, step_; int prevNote_, prevInst_, prevVol_; Step::PlainEffect prevEff_[Step::N_EFFECT]; }; BambooTracker-0.6.5/BambooTracker/command/pattern/erase_volume_in_step_command.cpp000066400000000000000000000014441476276175200305000ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "erase_volume_in_step_command.hpp" #include "pattern_command_utils.hpp" EraseVolumeInStepCommand::EraseVolumeInStepCommand( std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum) : AbstractCommand(CommandId::EraseVolumeInStep), mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum) { prevVol_ = command_utils::getStep(mod, songNum, trackNum, orderNum, stepNum).getVolume(); } bool EraseVolumeInStepCommand::redo() { command_utils::getStep(mod_, song_, track_, order_, step_).clearVolume(); return true; } bool EraseVolumeInStepCommand::undo() { command_utils::getStep(mod_, song_, track_, order_, step_).setVolume(prevVol_); return true; } BambooTracker-0.6.5/BambooTracker/command/pattern/erase_volume_in_step_command.hpp000066400000000000000000000007571476276175200305130ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include "../abstract_command.hpp" #include "module.hpp" class EraseVolumeInStepCommand final : public AbstractCommand { public: EraseVolumeInStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, track_, order_, step_; int prevVol_; }; BambooTracker-0.6.5/BambooTracker/command/pattern/expand_pattern_command.cpp000066400000000000000000000050471476276175200273100ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "expand_pattern_command.hpp" #include "pattern_command_utils.hpp" ExpandPatternCommand::ExpandPatternCommand( std::weak_ptr mod, int songNum, int beginTrack, int beginColumn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep) : AbstractCommand(CommandId::ExpandPattern), mod_(mod), song_(songNum), bTrack_(beginTrack), bCol_(beginColumn), order_(beginOrder), bStep_(beginStep) { auto& song = mod.lock()->getSong(songNum); std::size_t h = static_cast(endStep - beginStep + 1); std::size_t w = command_utils::calculateColumnSize(beginTrack, beginColumn, endTrack, endColumn); prevCells_ = command_utils::getPreviousCells(song, w, h, beginTrack, beginColumn, beginOrder, beginStep); } bool ExpandPatternCommand::redo() { auto& song = mod_.lock()->getSong(song_); int stepIndex = bStep_; for (std::size_t i = 0; i < prevCells_.rowSize(); ++i) { int trackIndex = bTrack_; int columnIndex = bCol_; for (std::size_t j = 0; j < prevCells_.columnSize(); ++j) { Step& step = command_utils::getStep(song, trackIndex, order_, stepIndex); switch (columnIndex) { case 0: { if (i % 2) { step.clearNoteNumber(); } else { step.setNoteNumber(std::stoi(prevCells_.at(i / 2).at(j))); } break; } case 1: { if (i % 2) { step.clearInstrumentNumber(); } else { step.setInstrumentNumber(std::stoi(prevCells_.at(i / 2).at(j))); } break; } case 2: { if (i % 2) { step.clearVolume(); } else { step.setVolume(std::stoi(prevCells_.at(i / 2).at(j))); } break; } default: { int effectColumnIndex = columnIndex - 3; int effectNumber = effectColumnIndex / 2; if (effectColumnIndex % 2) { // Effect value column. if (i % 2) { step.clearEffectValue(effectNumber); } else { step.setEffectValue(effectNumber, std::stoi(prevCells_.at(i / 2).at(j))); } } else { // Effect ID column. if (i % 2) { step.clearEffectId(effectNumber); } else { step.setEffectId(effectNumber, prevCells_.at(i / 2).at(j)); } } break; } } trackIndex += (++columnIndex / Step::N_COLUMN); columnIndex %= Step::N_COLUMN; } ++stepIndex; } return true; } bool ExpandPatternCommand::undo() { try { command_utils::restorePattern(mod_.lock()->getSong(song_), prevCells_, bTrack_, bCol_, order_, bStep_); return true; } catch (...) { return false; } } BambooTracker-0.6.5/BambooTracker/command/pattern/expand_pattern_command.hpp000066400000000000000000000011661476276175200273130ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "module.hpp" #include "vector_2d.hpp" class ExpandPatternCommand final : public AbstractCommand { public: ExpandPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginColumn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, bTrack_, bCol_, order_, bStep_; Vector2d prevCells_; }; BambooTracker-0.6.5/BambooTracker/command/pattern/insert_step_command.cpp000066400000000000000000000012431476276175200266250ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "insert_step_command.hpp" #include "pattern_command_utils.hpp" InsertStepCommand::InsertStepCommand( std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum) : AbstractCommand(CommandId::InsertStep), mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum) { } bool InsertStepCommand::redo() { command_utils::getPattern(mod_, song_, track_, order_).insertStep(step_); return true; } bool InsertStepCommand::undo() { command_utils::getPattern(mod_, song_, track_, order_).deletePreviousStep(step_ + 1); return true; } BambooTracker-0.6.5/BambooTracker/command/pattern/insert_step_command.hpp000066400000000000000000000007121476276175200266320ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include "../abstract_command.hpp" #include "module.hpp" class InsertStepCommand final : public AbstractCommand { public: InsertStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, track_, order_, step_; }; BambooTracker-0.6.5/BambooTracker/command/pattern/interpolate_pattern_command.cpp000066400000000000000000000063511476276175200303560ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "interpolate_pattern_command.hpp" #include "pattern_command_utils.hpp" namespace { inline int interp(int a, int b, size_t t, int div) { return a + (b - a) * static_cast(t) / div; } } InterpolatePatternCommand::InterpolatePatternCommand( std::weak_ptr mod, int songNum, int beginTrack, int beginColumn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep) : AbstractCommand(CommandId::InterpolatePattern), mod_(mod), song_(songNum), bTrack_(beginTrack), bCol_(beginColumn), order_(beginOrder), bStep_(beginStep), eStep_(endStep) { auto& song = mod.lock()->getSong(songNum); std::size_t h = static_cast(endStep - beginStep + 1); std::size_t w = command_utils::calculateColumnSize(beginTrack, beginColumn, endTrack, endColumn); prevCells_ = command_utils::getPreviousCells(song, w, h, beginTrack, beginColumn, beginOrder, beginStep); } bool InterpolatePatternCommand::redo() { auto& song = mod_.lock()->getSong(song_); int div = static_cast(prevCells_.rowSize()) - 1; if (!div) div = 1; int trackIndex = bTrack_; int columnIndex = bCol_; for (std::size_t i = 0; i < prevCells_.columnSize(); ++i) { int stepIndex = bStep_; for (std::size_t j = 0; j < prevCells_.rowSize(); ++j) { Pattern& pattern = command_utils::getPattern(song, trackIndex, order_); Step& firstStep = pattern.getStep(bStep_); Step& lastStep = pattern.getStep(eStep_); switch (columnIndex) { case 0: { if (firstStep.hasGeneralNote() && lastStep.hasGeneralNote()) { pattern.getStep(stepIndex).setNoteNumber( interp(firstStep.getNoteNumber(), lastStep.getNoteNumber(), j, div)); } break; } case 1: { if (firstStep.hasInstrument() && lastStep.hasInstrument()) { pattern.getStep(stepIndex).setInstrumentNumber( interp(firstStep.getInstrumentNumber(), lastStep.getInstrumentNumber(), j, div)); } break; } case 2: { if (firstStep.hasVolume() && lastStep.hasVolume()) { pattern.getStep(stepIndex).setVolume( interp(firstStep.getVolume(), lastStep.getVolume(), j, div)); } break; } default: { int effectColumnIndex = columnIndex - 3; int effectNumber = effectColumnIndex / 2; if (effectColumnIndex % 2) { // Effect value column. if (firstStep.hasEffectValue(effectNumber) && lastStep.hasEffectValue(effectNumber)) { pattern.getStep(stepIndex).setEffectValue( effectNumber, interp(firstStep.getEffectValue(effectNumber), lastStep.getEffectValue(effectNumber), j, div)); } } else { // Effect ID column. std::string firstId = firstStep.getEffectId(effectNumber); std::string lastId = lastStep.getEffectId(effectNumber); if (firstId == lastId) { pattern.getStep(stepIndex).setEffectId(effectNumber, firstId); } } break; } } ++stepIndex; } ++columnIndex; trackIndex += (columnIndex / Step::N_COLUMN); columnIndex %= Step::N_COLUMN; } return true; } bool InterpolatePatternCommand::undo() { try { command_utils::restorePattern(mod_.lock()->getSong(song_), prevCells_, bTrack_, bCol_, order_, bStep_); return true; } catch (...) { return false; } } BambooTracker-0.6.5/BambooTracker/command/pattern/interpolate_pattern_command.hpp000066400000000000000000000012211476276175200303520ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "module.hpp" #include "vector_2d.hpp" class InterpolatePatternCommand final : public AbstractCommand { public: InterpolatePatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginColumn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, bTrack_, bCol_, order_, bStep_; int eStep_; Vector2d prevCells_; }; BambooTracker-0.6.5/BambooTracker/command/pattern/paste_copied_data_to_pattern_command.cpp000066400000000000000000000022471476276175200321620ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "paste_copied_data_to_pattern_command.hpp" #include "pattern_command_utils.hpp" PasteCopiedDataToPatternCommand::PasteCopiedDataToPatternCommand( std::weak_ptr mod, int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, const Vector2d& cells) : AbstractCommand(CommandId::PasteCopiedDataToPattern), mod_(mod), song_(songNum), track_(beginTrack), col_(beginColmn), order_(beginOrder), step_(beginStep), cells_(cells) { auto& song = mod.lock()->getSong(songNum); prevCells_ = command_utils::getPreviousCells(song, cells.columnSize(), cells.rowSize(), beginTrack, beginColmn, beginOrder, beginStep); } bool PasteCopiedDataToPatternCommand::redo() { try { command_utils::restorePattern(mod_.lock()->getSong(song_), cells_, track_, col_, order_, step_); return true; } catch (...) { return false; } } bool PasteCopiedDataToPatternCommand::undo() { try { command_utils::restorePattern(mod_.lock()->getSong(song_), prevCells_, track_, col_, order_, step_); return true; } catch (...) { return false; } } BambooTracker-0.6.5/BambooTracker/command/pattern/paste_copied_data_to_pattern_command.hpp000066400000000000000000000012031476276175200321560ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "module.hpp" #include "vector_2d.hpp" class PasteCopiedDataToPatternCommand final : public AbstractCommand { public: PasteCopiedDataToPatternCommand( std::weak_ptr mod, int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, const Vector2d& cells); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, track_, col_, order_, step_; Vector2d cells_, prevCells_; }; BambooTracker-0.6.5/BambooTracker/command/pattern/paste_insert_copied_data_to_pattern_command.cpp000066400000000000000000000032061476276175200335420ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2020 Rerrah * SPDX-License-Identifier: MIT */ #include "paste_insert_copied_data_to_pattern_command.hpp" #include #include #include "pattern_command_utils.hpp" PasteInsertCopiedDataToPatternCommand::PasteInsertCopiedDataToPatternCommand( std::weak_ptr mod, int songNum, int beginTrack, int beginColumn, int beginOrder, int beginStep, const Vector2d& cells) : AbstractCommand(CommandId::PasteInsertCopiedDataToPattern), mod_(mod), song_(songNum), track_(beginTrack), col_(beginColumn), order_(beginOrder), step_(beginStep) { auto& song = mod.lock()->getSong(songNum); size_t newStepSize = song.getTrack(track_).getPatternFromOrderNumber(order_).getSize() - step_; prevCells_ = command_utils::getPreviousCells(song, cells.columnSize(), newStepSize, beginTrack, beginColumn, beginOrder, beginStep); Vector2d newCells(newStepSize, cells.columnSize()); std::size_t shiftedRowIndex = std::min(cells.rowSize(), newStepSize); std::copy_n(cells.begin(), shiftedRowIndex, newCells.begin()); std::copy_n(prevCells_.begin(), newStepSize - shiftedRowIndex, newCells.begin() + shiftedRowIndex); cells_ = newCells; } bool PasteInsertCopiedDataToPatternCommand::redo() { try { command_utils::restorePattern(mod_.lock()->getSong(song_), cells_, track_, col_, order_, step_); return true; } catch (...) { return false; } } bool PasteInsertCopiedDataToPatternCommand::undo() { try { command_utils::restorePattern(mod_.lock()->getSong(song_), prevCells_, track_, col_, order_, step_); return true; } catch (...) { return false; } } BambooTracker-0.6.5/BambooTracker/command/pattern/paste_insert_copied_data_to_pattern_command.hpp000066400000000000000000000012201476276175200335410ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2020 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "module.hpp" #include "vector_2d.hpp" class PasteInsertCopiedDataToPatternCommand final : public AbstractCommand { public: PasteInsertCopiedDataToPatternCommand( std::weak_ptr mod, int songNum, int beginTrack, int beginColumn, int beginOrder, int beginStep, const Vector2d& cells); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, track_, col_, order_, step_; Vector2d cells_, prevCells_; }; BambooTracker-0.6.5/BambooTracker/command/pattern/paste_mix_copied_data_to_pattern_command.cpp000066400000000000000000000055641476276175200330440ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "paste_mix_copied_data_to_pattern_command.hpp" #include "pattern_command_utils.hpp" PasteMixCopiedDataToPatternCommand::PasteMixCopiedDataToPatternCommand( std::weak_ptr mod, int songNum, int beginTrack, int beginColumn, int beginOrder, int beginStep, const Vector2d& cells) : AbstractCommand(CommandId::PasteMixCopiedDataToPattern), mod_(mod), song_(songNum), track_(beginTrack), col_(beginColumn), order_(beginOrder), step_(beginStep), cells_(cells) { auto& song = mod.lock()->getSong(songNum); prevCells_ = command_utils::getPreviousCells(song, cells.columnSize(), cells.rowSize(), beginTrack, beginColumn, beginOrder, beginStep); } bool PasteMixCopiedDataToPatternCommand::redo() { try { auto& song = mod_.lock()->getSong(song_); int stepIndex = step_; int orderIndex = order_; for (const auto& row : cells_) { int trackIndex = track_; int columnIndex = col_; for (const std::string& cell : row) { if (static_cast(stepIndex) >= song.getTrack(trackIndex).getPatternFromOrderNumber(orderIndex).getSize()) { if (static_cast(++orderIndex) < song.getTrack(trackIndex).getOrderSize()) { stepIndex = 0; } else { return true; } } Step& step = command_utils::getStep(song, trackIndex, orderIndex, stepIndex); switch (columnIndex) { case 0: { int n = std::stoi(cell); if (!Step::testEmptyNote(n) && step.isEmptyNote()) { step.setNoteNumber(n); } break; } case 1: { int n = std::stoi(cell); if (!Step::testEmptyInstrument(n) && !step.hasInstrument()) { step.setInstrumentNumber(n); } break; } case 2: { int volume = std::stoi(cell); if (!Step::testEmptyVolume(volume) && !step.hasVolume()) { step.setVolume(volume); } break; } default: { int effectColumnIndex = columnIndex - 3; int effectNumber = effectColumnIndex / 2; if (effectColumnIndex % 2) { // Effect value column. int value = std::stoi(cell); if (!Step::testEmptyEffectValue(value) && !step.hasEffectValue(effectNumber)) { step.setEffectValue(effectNumber, value); } } else { // Effect ID column. if (!Step::testEmptyEffectId(cell) && !step.hasEffectId(effectNumber)) { step.setEffectId(effectNumber, cell); } } break; } } ++columnIndex; trackIndex += (columnIndex / Step::N_COLUMN); columnIndex %= Step::N_COLUMN; } ++stepIndex; } return true; } catch (...) { return false; } } bool PasteMixCopiedDataToPatternCommand::undo() { try { command_utils::restorePattern(mod_.lock()->getSong(song_), prevCells_, track_, col_, order_, step_); return true; } catch (...) { return false; } } BambooTracker-0.6.5/BambooTracker/command/pattern/paste_mix_copied_data_to_pattern_command.hpp000066400000000000000000000012121476276175200330330ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "module.hpp" #include "vector_2d.hpp" class PasteMixCopiedDataToPatternCommand final : public AbstractCommand { public: PasteMixCopiedDataToPatternCommand( std::weak_ptr mod, int songNum, int beginTrack, int beginColumn, int beginOrder, int beginStep, const Vector2d& cells); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, track_, col_, order_, step_; Vector2d cells_, prevCells_; }; BambooTracker-0.6.5/BambooTracker/command/pattern/paste_overwrite_copied_data_to_pattern_command.cpp000066400000000000000000000052131476276175200342640ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "paste_overwrite_copied_data_to_pattern_command.hpp" #include "pattern_command_utils.hpp" PasteOverwriteCopiedDataToPatternCommand::PasteOverwriteCopiedDataToPatternCommand( std::weak_ptr mod, int songNum, int beginTrack, int beginColumn, int beginOrder, int beginStep, const Vector2d& cells) : AbstractCommand(CommandId::PasteOverwriteCopiedDataToPattern), mod_(mod), song_(songNum), track_(beginTrack), col_(beginColumn), order_(beginOrder), step_(beginStep), cells_(cells) { auto& song = mod.lock()->getSong(songNum); prevCells_ = command_utils::getPreviousCells(song, cells.columnSize(), cells.rowSize(), beginTrack, beginColumn, beginOrder, beginStep); } bool PasteOverwriteCopiedDataToPatternCommand::redo() { auto& sng = mod_.lock()->getSong(song_); int stepIndex = step_; int orderIndex = order_; for (const auto& row : cells_) { int trackIndex = track_; int columnIndex = col_; for (const std::string& cell : row) { if (static_cast(stepIndex) >= sng.getTrack(trackIndex).getPatternFromOrderNumber(orderIndex).getSize()) { if (static_cast(++orderIndex) < sng.getTrack(trackIndex).getOrderSize()) { stepIndex = 0; } else { return true; } } Step& step = command_utils::getStep(sng, trackIndex, orderIndex, stepIndex); switch (columnIndex) { case 0: { int n = std::stoi(cell); if (!Step::testEmptyNote(n)) { step.setNoteNumber(n); } break; } case 1: { int n = std::stoi(cell); if (!Step::testEmptyInstrument(n)) { step.setInstrumentNumber(n); } break; } case 2: { int volume = std::stoi(cell); if (!Step::testEmptyVolume(volume)) { step.setVolume(volume); } break; } default: { int effectColumnIndex = columnIndex - 3; int effectNumber = effectColumnIndex / 2; if (effectColumnIndex % 2) { // Effect value column. int value = std::stoi(cell); if (!Step::testEmptyEffectValue(value)) { step.setEffectValue(effectNumber, value); } } else { // Effect ID column. if (!Step::testEmptyEffectId(cell)) { step.setEffectId(effectNumber, cell); } } break; } } trackIndex += (++columnIndex / Step::N_COLUMN); columnIndex %= Step::N_COLUMN; } ++stepIndex; } return true; } bool PasteOverwriteCopiedDataToPatternCommand::undo() { try { command_utils::restorePattern(mod_.lock()->getSong(song_), prevCells_, track_, col_, order_, step_); return true; } catch (...) { return false; } } BambooTracker-0.6.5/BambooTracker/command/pattern/paste_overwrite_copied_data_to_pattern_command.hpp000066400000000000000000000012261476276175200342710ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "module.hpp" #include "vector_2d.hpp" class PasteOverwriteCopiedDataToPatternCommand final : public AbstractCommand { public: PasteOverwriteCopiedDataToPatternCommand( std::weak_ptr mod, int songNum, int beginTrack, int beginColumn, int beginOrder, int beginStep, const Vector2d& cells); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, track_, col_, order_, step_; Vector2d cells_, prevCells_; }; BambooTracker-0.6.5/BambooTracker/command/pattern/pattern_command_utils.cpp000066400000000000000000000064661476276175200271770ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2020 Rerrah * SPDX-License-Identifier: MIT */ #include "pattern_command_utils.hpp" namespace command_utils { size_t calculateColumnSize(int beginTrack, int beginColumn, int endTrack, int endColumn) { constexpr int WCOL = Step::N_COLUMN - 1; int w = 0; int tr = endTrack; int cl = endColumn; while (true) { if (tr == beginTrack) { w += (cl - beginColumn + 1); break; } else { w += (cl + 1); cl = WCOL; --tr; } } return static_cast(w); } Vector2d getPreviousCells(Song& song, std::size_t w, std::size_t h, int beginTrack, int beginColumn, int beginOrder, int beginStep) { Vector2d cells(h, w); int stepIndex = beginStep; for (std::size_t i = 0; i < h; ++i) { int trackIndex = beginTrack; int columnIndex = beginColumn; for (std::size_t j = 0; j < w; ++j) { if (static_cast(stepIndex) >= song.getTrack(trackIndex).getPatternFromOrderNumber(beginOrder).getSize()) { if (static_cast(++beginOrder) < song.getTrack(trackIndex).getOrderSize()) { stepIndex = 0; } else { return cells; } } Step& step = song.getTrack(trackIndex).getPatternFromOrderNumber(beginOrder).getStep(stepIndex); std::string value; switch (columnIndex) { case 0: value = std::to_string(step.getNoteNumber()); break; case 1: value = std::to_string(step.getInstrumentNumber()); break; case 2: value = std::to_string(step.getVolume()); break; default: { int effectColumnIndex = columnIndex - 3; int effectNumber = effectColumnIndex / 2; if (effectColumnIndex % 2) { // Effect value column. value = std::to_string(step.getEffectValue(effectNumber)); } else { // Effect ID column. value = step.getEffectId(effectNumber); } break; } } cells[i][j] = value; trackIndex += (++columnIndex / Step::N_COLUMN); columnIndex %= Step::N_COLUMN; } ++stepIndex; } return cells; } void restorePattern(Song& song, const Vector2d& cells, int beginTrack, int beginColumn, int beginOrder, int beginStep) { int stepIndex = beginStep; for (const auto& row : cells) { int trackIndex = beginTrack; int columnIndex = beginColumn; for (const std::string& cell : row) { if (static_cast(stepIndex) >= song.getTrack(trackIndex).getPatternFromOrderNumber(beginOrder).getSize()) { if (static_cast(++beginOrder) < song.getTrack(trackIndex).getOrderSize()) { stepIndex = 0; } else { return; } } Step& step = song.getTrack(trackIndex).getPatternFromOrderNumber(beginOrder).getStep(stepIndex); switch (columnIndex) { case 0: step.setNoteNumber(std::stoi(cell)); break; case 1: step.setInstrumentNumber(std::stoi(cell)); break; case 2: step.setVolume(std::stoi(cell)); break; default: { int effectColumnIndex = columnIndex - 3; int effectNumber = effectColumnIndex / 2; if (effectColumnIndex % 2) { // Effect value column. step.setEffectValue(effectNumber, std::stoi(cell)); } else { // Effect ID column. step.setEffectId(effectNumber, cell); } break; } } trackIndex += (++columnIndex / Step::N_COLUMN); columnIndex %= Step::N_COLUMN; } ++stepIndex; } } } BambooTracker-0.6.5/BambooTracker/command/pattern/pattern_command_utils.hpp000066400000000000000000000025311476276175200271710ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2020 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "module.hpp" #include "vector_2d.hpp" namespace command_utils { inline Step& getStep(std::weak_ptr mod, int song, int track, int order, int step) { return mod.lock()->getSong(song).getTrack(track) .getPatternFromOrderNumber(order).getStep(step); } inline Step& getStep(Song& song, int track, int order, int step) { return song.getTrack(track).getPatternFromOrderNumber(order).getStep(step); } inline Pattern& getPattern(std::weak_ptr mod, int song, int track, int order) { return mod.lock()->getSong(song).getTrack(track).getPatternFromOrderNumber(order); } inline Pattern& getPattern(Song& song, int track, int order) { return song.getTrack(track).getPatternFromOrderNumber(order); } size_t calculateColumnSize(int beginTrack, int beginColumn, int endTrack, int endColumn); Vector2d getPreviousCells(Song& song, std::size_t w, std::size_t h, int beginTrack, int beginColumn, int beginOrder, int beginStep); /** * @throw @c std::invalid_argument or @c std::out_of_range if @c cells contain invalid data. */ void restorePattern(Song& song, const Vector2d& cells, int beginTrack, int beginColumn, int beginOrder, int beginStep); } BambooTracker-0.6.5/BambooTracker/command/pattern/replace_instrument_in_pattern_command.cpp000066400000000000000000000031321476276175200324130ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "replace_instrument_in_pattern_command.hpp" #include "pattern_command_utils.hpp" ReplaceInstrumentInPatternCommand::ReplaceInstrumentInPatternCommand( std::weak_ptr mod, int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep, int newInst) : AbstractCommand(CommandId::ReplaceInstrumentInPattern), mod_(mod), song_(songNum), bTrack_(beginTrack), order_(beginOrder), bStep_(beginStep), eTrack_(endTrack), eStep_(endStep), inst_(newInst) { auto& sng = mod.lock()->getSong(songNum); for (int step = beginStep; step <= endStep; ++step) { for (int track = beginTrack; track <= endTrack; ++track) { Step& st = command_utils::getStep(sng, track, beginOrder, step); if (st.hasInstrument()) prevInsts_.push_back(st.getInstrumentNumber()); } } } bool ReplaceInstrumentInPatternCommand::redo() { auto& sng = mod_.lock()->getSong(song_); for (int step = bStep_; step <= eStep_; ++step) { for (int track = bTrack_; track <= eTrack_; ++track) { Step& st = command_utils::getStep(sng, track, order_, step); if (st.hasInstrument()) st.setInstrumentNumber(inst_); } } return true; } bool ReplaceInstrumentInPatternCommand::undo() { auto& sng = mod_.lock()->getSong(song_); size_t i = 0; for (int step = bStep_; step <= eStep_; ++step) { for (int track = bTrack_; track <= eTrack_; ++track) { Step& st = command_utils::getStep(sng, track, order_, step); if (st.hasInstrument()) st.setInstrumentNumber(prevInsts_.at(i++)); } } return true; } BambooTracker-0.6.5/BambooTracker/command/pattern/replace_instrument_in_pattern_command.hpp000066400000000000000000000012071476276175200324210ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "module.hpp" class ReplaceInstrumentInPatternCommand final : public AbstractCommand { public: ReplaceInstrumentInPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep, int newInst); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_; int bTrack_, order_, bStep_; int eTrack_, eStep_; int inst_; std::vector prevInsts_; }; BambooTracker-0.6.5/BambooTracker/command/pattern/reverse_pattern_command.cpp000066400000000000000000000044051476276175200275010ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "reverse_pattern_command.hpp" #include "pattern_command_utils.hpp" ReversePatternCommand::ReversePatternCommand( std::weak_ptr mod, int songNum, int beginTrack, int beginColumn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep) : AbstractCommand(CommandId::ReversePattern), mod_(mod), song_(songNum), bTrack_(beginTrack), bCol_(beginColumn), order_(beginOrder), bStep_(beginStep) { auto& song = mod.lock()->getSong(songNum); std::size_t h = static_cast(endStep - beginStep + 1); std::size_t w = command_utils::calculateColumnSize(beginTrack, beginColumn, endTrack, endColumn); prevCells_ = command_utils::getPreviousCells(song, w, h, beginTrack, beginColumn, beginOrder, beginStep); } bool ReversePatternCommand::redo() { auto& song = mod_.lock()->getSong(song_); std::size_t lastRowIndex = prevCells_.rowSize() - 1; int stepIndex = bStep_; for (std::size_t i = 0; i < prevCells_.rowSize(); ++i) { int trackIndex = bTrack_; int columnIndex = bCol_; for (std::size_t j = 0; j < prevCells_.columnSize(); ++j) { Step& step = command_utils::getStep(song, trackIndex, order_, stepIndex); switch (columnIndex) { case 0: step.setNoteNumber(std::stoi(prevCells_.at(lastRowIndex - i).at(j))); break; case 1: step.setInstrumentNumber(std::stoi(prevCells_.at(lastRowIndex - i).at(j))); break; case 2: step.setVolume(std::stoi(prevCells_.at(lastRowIndex - i).at(j))); break; default: { int effectColumnIndex = columnIndex - 3; int effectNumber = effectColumnIndex / 2; if (effectColumnIndex % 2) { // Effect value column. step.setEffectValue(effectNumber, std::stoi(prevCells_.at(lastRowIndex - i).at(j))); } else { // Effect ID column. step.setEffectId(effectNumber, prevCells_.at(lastRowIndex - i).at(j)); } break; } } trackIndex += (++columnIndex / Step::N_COLUMN); columnIndex %= Step::N_COLUMN; } ++stepIndex; } return true; } bool ReversePatternCommand::undo() { try { command_utils::restorePattern(mod_.lock()->getSong(song_), prevCells_, bTrack_, bCol_, order_, bStep_); return true; } catch (...) { return false; } } BambooTracker-0.6.5/BambooTracker/command/pattern/reverse_pattern_command.hpp000066400000000000000000000011721476276175200275040ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "module.hpp" #include "vector_2d.hpp" class ReversePatternCommand final : public AbstractCommand { public: ReversePatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginColumn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, bTrack_, bCol_, order_, bStep_; Vector2d prevCells_; }; BambooTracker-0.6.5/BambooTracker/command/pattern/set_echo_buffer_access_command.cpp000066400000000000000000000015351476276175200307350ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "set_echo_buffer_access_command.hpp" #include "pattern_command_utils.hpp" SetEchoBufferAccessCommand::SetEchoBufferAccessCommand( std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int bufNum) : AbstractCommand(CommandId::SetEchoBufferAccess), mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum), buf_(bufNum) { prevNote_ = command_utils::getStep(mod, songNum, trackNum, orderNum, stepNum).getNoteNumber(); } bool SetEchoBufferAccessCommand::redo() { command_utils::getStep(mod_, song_, track_, order_, step_).setEchoBuffer(buf_); return true; } bool SetEchoBufferAccessCommand::undo() { command_utils::getStep(mod_, song_, track_, order_, step_).setNoteNumber(prevNote_); return true; } BambooTracker-0.6.5/BambooTracker/command/pattern/set_echo_buffer_access_command.hpp000066400000000000000000000010321476276175200307320ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "module.hpp" class SetEchoBufferAccessCommand final : public AbstractCommand { public: SetEchoBufferAccessCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int bufNum); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, track_, order_, step_, buf_; int prevNote_; }; BambooTracker-0.6.5/BambooTracker/command/pattern/set_effect_id_to_step_command.cpp000066400000000000000000000035261476276175200306140ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "set_effect_id_to_step_command.hpp" #include "pattern_command_utils.hpp" SetEffectIDToStepCommand::SetEffectIDToStepCommand( std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int n, std::string id, bool fillValue00, bool secondEntry) : AbstractCommand(CommandId::SetEffectIDToStep), mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum), n_(n), effID_(id), isSecondEntry_(secondEntry) { Step& step = command_utils::getStep(mod, songNum, trackNum, orderNum, stepNum); prevEffID_ = step.getEffectId(n); filledValue00_ = fillValue00 && !step.hasEffectValue(n); } bool SetEffectIDToStepCommand::redo() { std::string str = isSecondEntry_ ? effID_ : ("0" + effID_); Step& step = command_utils::getStep(mod_, song_, track_, order_, step_); step.setEffectId(n_, str); if (filledValue00_) step.setEffectValue(n_, 0); return true; } bool SetEffectIDToStepCommand::undo() { Step& step = command_utils::getStep(mod_, song_, track_, order_, step_); step.setEffectId(n_, prevEffID_); if (filledValue00_) step.clearEffectValue(n_); if (!isSecondEntry_) { // Forced complete effID_ = "0" + effID_; isSecondEntry_ = true; } return true; } bool SetEffectIDToStepCommand::mergeWith(const AbstractCommand* other) { if (other->getID() == getID() && !isSecondEntry_) { auto com = dynamic_cast(other); if (com->song_ == song_ && com->track_ == track_ && com->order_ == order_ && com->step_ == step_ && com->n_ == n_ && com->isSecondEntry_) { effID_ = effID_ + com->effID_; isSecondEntry_ = true; redo(); return true; } } // Enterd only 1 character if (!isSecondEntry_) { effID_ = "0" + effID_; isSecondEntry_ = true; } return false; } BambooTracker-0.6.5/BambooTracker/command/pattern/set_effect_id_to_step_command.hpp000066400000000000000000000012731476276175200306160ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "module.hpp" class SetEffectIDToStepCommand final : public AbstractCommand { public: SetEffectIDToStepCommand( std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int n, std::string id, bool fillValue00, bool secondEntry); bool redo() override; bool undo() override; bool mergeWith(const AbstractCommand* other) override; private: std::weak_ptr mod_; const int song_, track_, order_, step_, n_; std::string effID_, prevEffID_; bool filledValue00_; bool isSecondEntry_; }; BambooTracker-0.6.5/BambooTracker/command/pattern/set_effect_value_to_step_command.cpp000066400000000000000000000035031476276175200313270ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "set_effect_value_to_step_command.hpp" #include "pattern_command_utils.hpp" #include "effect.hpp" SetEffectValueToStepCommand::SetEffectValueToStepCommand( std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int n, int value, EffectDisplayControl ctrl, bool secondEntry) : AbstractCommand(CommandId::SetEffectValueToStep), mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum), n_(n), val_(value), ctrl_(ctrl), isSecondEntry_(secondEntry) { prevVal_ = command_utils::getStep(mod, songNum, trackNum, orderNum, stepNum).getEffectValue(n); } bool SetEffectValueToStepCommand::redo() { int value; switch (ctrl_) { default: case EffectDisplayControl::Unset: value = val_; break; case EffectDisplayControl::ReverseFMVolumeDelay: value = effect_utils::reverseFmVolume(val_); break; case EffectDisplayControl::ReverseFMBrightness: value = effect_utils::reverseFmBrightness(val_); break; } command_utils::getStep(mod_, song_, track_, order_, step_).setEffectValue(n_, value); return true; } bool SetEffectValueToStepCommand::undo() { command_utils::getStep(mod_, song_, track_, order_, step_).setEffectValue(n_, prevVal_); isSecondEntry_ = true; // Forced complete return true; } bool SetEffectValueToStepCommand::mergeWith(const AbstractCommand* other) { if (other->getID() == getID() && !isSecondEntry_) { auto com = dynamic_cast(other); if (com->song_ == song_ && com->track_ == track_ && com->order_ == order_ && com->step_ == step_ && com->n_ == n_ && com->isSecondEntry_) { val_ = (val_ << 4) + com->val_; redo(); isSecondEntry_ = true; return true; } } isSecondEntry_ = true; return false; } BambooTracker-0.6.5/BambooTracker/command/pattern/set_effect_value_to_step_command.hpp000066400000000000000000000014251476276175200313350ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include "../abstract_command.hpp" #include "module.hpp" enum class EffectDisplayControl { Unset, ReverseFMVolumeDelay, ReverseFMBrightness }; class SetEffectValueToStepCommand final : public AbstractCommand { public: SetEffectValueToStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int n, int value, EffectDisplayControl ctrl, bool secondEntry); bool redo() override; bool undo() override; bool mergeWith(const AbstractCommand* other) override; private: std::weak_ptr mod_; const int song_, track_, order_, step_, n_; int val_, prevVal_; const EffectDisplayControl ctrl_; bool isSecondEntry_; }; BambooTracker-0.6.5/BambooTracker/command/pattern/set_instrument_to_step_command.cpp000066400000000000000000000026521476276175200311130ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "set_instrument_to_step_command.hpp" #include "pattern_command_utils.hpp" SetInstrumentToStepCommand::SetInstrumentToStepCommand( std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int instNum, bool secondEntry) : AbstractCommand(CommandId::SetInstrumentInStep), mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum), inst_(instNum), prevInst_(command_utils::getStep(mod, songNum, trackNum, orderNum, stepNum).getInstrumentNumber()), isSecondEntry_(secondEntry) { } bool SetInstrumentToStepCommand::redo() { command_utils::getStep(mod_, song_, track_, order_, step_).setInstrumentNumber(inst_); return true; } bool SetInstrumentToStepCommand::undo() { command_utils::getStep(mod_, song_, track_, order_, step_).setInstrumentNumber(prevInst_); isSecondEntry_ = true; // Forced complete return true; } bool SetInstrumentToStepCommand::mergeWith(const AbstractCommand* other) { if (other->getID() == getID() && !isSecondEntry_) { auto com = dynamic_cast(other); if (com->song_ == song_ && com->track_ == track_ && com->order_ == order_ && com->step_ == step_ && com->isSecondEntry_) { inst_ = (inst_ << 4) + com->inst_; redo(); isSecondEntry_ = true; return true; } } isSecondEntry_ = true; return false; } BambooTracker-0.6.5/BambooTracker/command/pattern/set_instrument_to_step_command.hpp000066400000000000000000000011731476276175200311150ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include "../abstract_command.hpp" #include "module.hpp" class SetInstrumentToStepCommand final : public AbstractCommand { public: SetInstrumentToStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int instNum, bool secondEntry); bool redo() override; bool undo() override; bool mergeWith(const AbstractCommand* other) override; private: std::weak_ptr mod_; const int song_, track_, order_, step_; int inst_; const int prevInst_; bool isSecondEntry_; }; BambooTracker-0.6.5/BambooTracker/command/pattern/set_key_cut_to_step_command.cpp000066400000000000000000000023441476276175200303440ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2022 Rerrah * SPDX-License-Identifier: MIT */ #include "set_key_cut_to_step_command.hpp" #include "pattern_command_utils.hpp" SetKeyCutToStepCommand::SetKeyCutToStepCommand( std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum) : AbstractCommand(CommandId::SetKeyCutToStep), mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum) { Step& st = command_utils::getStep(mod, songNum, trackNum, orderNum, stepNum); prevNote_ = st.getNoteNumber(); prevInst_ = st.getInstrumentNumber(); prevVol_ = st.getVolume(); for (int i = 0; i < Step::N_EFFECT; ++i) { prevEff_[i] = st.getEffect(i); } } bool SetKeyCutToStepCommand::redo() { Step& st = command_utils::getStep(mod_, song_, track_, order_, step_); st.setKeyCut(); st.clearInstrumentNumber(); st.clearVolume(); for (int i = 0; i < Step::N_EFFECT; ++i) { st.clearEffect(i); } return true; } bool SetKeyCutToStepCommand::undo() { Step& st = command_utils::getStep(mod_, song_, track_, order_, step_); st.setNoteNumber(prevNote_); st.setInstrumentNumber(prevInst_); st.setVolume(prevVol_); for (int i = 0; i < Step::N_EFFECT; ++i) { st.setEffect(i, prevEff_[i]); } return true; } BambooTracker-0.6.5/BambooTracker/command/pattern/set_key_cut_to_step_command.hpp000066400000000000000000000011021476276175200303400ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2022 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "module.hpp" class SetKeyCutToStepCommand final : public AbstractCommand { public: SetKeyCutToStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, track_, order_, step_; int prevNote_, prevInst_, prevVol_; Step::PlainEffect prevEff_[Step::N_EFFECT]; }; BambooTracker-0.6.5/BambooTracker/command/pattern/set_key_off_to_step_command.cpp000066400000000000000000000023441476276175200303230ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "set_key_off_to_step_command.hpp" #include "pattern_command_utils.hpp" SetKeyOffToStepCommand::SetKeyOffToStepCommand( std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum) : AbstractCommand(CommandId::SetKeyOffToStep), mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum) { Step& st = command_utils::getStep(mod, songNum, trackNum, orderNum, stepNum); prevNote_ = st.getNoteNumber(); prevInst_ = st.getInstrumentNumber(); prevVol_ = st.getVolume(); for (int i = 0; i < Step::N_EFFECT; ++i) { prevEff_[i] = st.getEffect(i); } } bool SetKeyOffToStepCommand::redo() { Step& st = command_utils::getStep(mod_, song_, track_, order_, step_); st.setKeyOff(); st.clearInstrumentNumber(); st.clearVolume(); for (int i = 0; i < Step::N_EFFECT; ++i) { st.clearEffect(i); } return true; } bool SetKeyOffToStepCommand::undo() { Step& st = command_utils::getStep(mod_, song_, track_, order_, step_); st.setNoteNumber(prevNote_); st.setInstrumentNumber(prevInst_); st.setVolume(prevVol_); for (int i = 0; i < Step::N_EFFECT; ++i) { st.setEffect(i, prevEff_[i]); } return true; } BambooTracker-0.6.5/BambooTracker/command/pattern/set_key_off_to_step_command.hpp000066400000000000000000000011011476276175200303160ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "module.hpp" class SetKeyOffToStepCommand final : public AbstractCommand { public: SetKeyOffToStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, track_, order_, step_; int prevNote_, prevInst_, prevVol_; Step::PlainEffect prevEff_[Step::N_EFFECT]; }; BambooTracker-0.6.5/BambooTracker/command/pattern/set_key_on_to_step_command.cpp000066400000000000000000000026071476276175200301670ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "set_key_on_to_step_command.hpp" #include "pattern_command_utils.hpp" #include "effect.hpp" SetKeyOnToStepCommand::SetKeyOnToStepCommand( std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int noteNum, bool instMask, int instNum, bool volMask, int vol, bool isFMReversed) : AbstractCommand(CommandId::SetKeyOnToStep), mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum), note_(noteNum), inst_(instNum), vol_(vol), instMask_(instMask), volMask_(volMask), isFMReversed_(isFMReversed) { Step& st = command_utils::getStep(mod, songNum, trackNum, orderNum, stepNum); prevNote_ = st.getNoteNumber(); if (!instMask) prevInst_ = st.getInstrumentNumber(); if (!volMask) prevVol_ = st.getVolume(); } bool SetKeyOnToStepCommand::redo() { Step& st = command_utils::getStep(mod_, song_, track_, order_, step_); st.setNoteNumber(note_); if (!instMask_) st.setInstrumentNumber(inst_); if (!volMask_) st.setVolume(isFMReversed_ ? effect_utils::reverseFmVolume(vol_) : vol_); return true; } bool SetKeyOnToStepCommand::undo() { Step& st = command_utils::getStep(mod_, song_, track_, order_, step_); st.setNoteNumber(prevNote_); if (!instMask_) st.setInstrumentNumber(prevInst_); if (!volMask_) st.setVolume(prevVol_); return true; } BambooTracker-0.6.5/BambooTracker/command/pattern/set_key_on_to_step_command.hpp000066400000000000000000000012251476276175200301670ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include "../abstract_command.hpp" #include "module.hpp" class SetKeyOnToStepCommand final : public AbstractCommand { public: SetKeyOnToStepCommand( std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int noteNum, bool instMask, int instNum, bool volMask, int vol, bool isFMReversed); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, track_, order_, step_, note_, inst_, vol_; int prevNote_, prevInst_, prevVol_; bool instMask_, volMask_; bool isFMReversed_; }; BambooTracker-0.6.5/BambooTracker/command/pattern/set_volume_to_step_command.cpp000066400000000000000000000027701476276175200302130ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "set_volume_to_step_command.hpp" #include "pattern_command_utils.hpp" #include "effect.hpp" SetVolumeToStepCommand::SetVolumeToStepCommand( std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int volume, bool isFMReversed, bool secondEntry) : AbstractCommand(CommandId::SetVolumeToStep), mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum), vol_(volume), prevVol_(command_utils::getStep(mod, songNum, trackNum, orderNum, stepNum).getVolume()), isFMReversed_(isFMReversed), isSecondEntry_(secondEntry) { } bool SetVolumeToStepCommand::redo() { int volume = isFMReversed_ ? effect_utils::reverseFmVolume(vol_) : vol_; command_utils::getStep(mod_, song_, track_, order_, step_).setVolume(volume); return true; } bool SetVolumeToStepCommand::undo() { command_utils::getStep(mod_, song_, track_, order_, step_).setVolume(prevVol_); isSecondEntry_ = true; // Forced complete return true; } bool SetVolumeToStepCommand::mergeWith(const AbstractCommand* other) { if (other->getID() == getID() && !isSecondEntry_) { auto com = dynamic_cast(other); if (com->song_ == song_ && com->track_ == track_ && com->order_ == order_ && com->step_ == step_ && com->isSecondEntry_) { vol_ = (vol_ << 4) + com->vol_; redo(); isSecondEntry_ = true; return true; } } isSecondEntry_ = true; return false; } BambooTracker-0.6.5/BambooTracker/command/pattern/set_volume_to_step_command.hpp000066400000000000000000000012351476276175200302130ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include "../abstract_command.hpp" #include "module.hpp" class SetVolumeToStepCommand final : public AbstractCommand { public: SetVolumeToStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int volume, bool isFMReversed, bool secondEntry); bool redo() override; bool undo() override; bool mergeWith(const AbstractCommand* other) override; private: std::weak_ptr mod_; const int song_, track_, order_, step_; int vol_; const int prevVol_; const bool isFMReversed_; bool isSecondEntry_; }; BambooTracker-0.6.5/BambooTracker/command/pattern/shrink_pattern_command.cpp000066400000000000000000000057261476276175200273330ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "shrink_pattern_command.hpp" #include "pattern_command_utils.hpp" ShrinkPatternCommand::ShrinkPatternCommand( std::weak_ptr mod, int songNum, int beginTrack, int beginColumn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep) : AbstractCommand(CommandId::ShrinkPattern), mod_(mod), song_(songNum), bTrack_(beginTrack), bCol_(beginColumn), order_(beginOrder), bStep_(beginStep), eStep_(endStep) { auto& song = mod.lock()->getSong(songNum); std::size_t h = static_cast(endStep - beginStep + 1); std::size_t w = command_utils::calculateColumnSize(beginTrack, beginColumn, endTrack, endColumn); prevCells_ = command_utils::getPreviousCells(song, w, h, beginTrack, beginColumn, beginOrder, beginStep); } bool ShrinkPatternCommand::redo() { auto& song = mod_.lock()->getSong(song_); int stepIndex = bStep_; for (std::size_t i = 0; i < prevCells_.rowSize(); i += 2) { int trackIndex = bTrack_; int columnIndex = bCol_; for (std::size_t j = 0; j < prevCells_.columnSize(); ++j) { Step& step = command_utils::getStep(song, trackIndex, order_, stepIndex); switch (columnIndex) { case 0: step.setNoteNumber(std::stoi(prevCells_.at(i).at(j))); break; case 1: step.setInstrumentNumber(std::stoi(prevCells_.at(i).at(j))); break; case 2: step.setVolume(std::stoi(prevCells_.at(i).at(j))); break; default: { int effectColumnIndex = columnIndex - 3; int effectNumber = effectColumnIndex / 2; if (effectColumnIndex % 2) { // Effect value column. step.setEffectValue(effectNumber, std::stoi(prevCells_.at(i).at(j))); } else { // Effect ID column. step.setEffectId(effectNumber, prevCells_.at(i).at(j)); } break; } } trackIndex += (++columnIndex / Step::N_COLUMN); columnIndex %= Step::N_COLUMN; } ++stepIndex; } for (; stepIndex <= eStep_; ++stepIndex) { int trackIndex = bTrack_; int columnIndex = bCol_; for (std::size_t j = 0; j < prevCells_.columnSize(); ++j) { Step& step = command_utils::getStep(song, trackIndex, order_, stepIndex); switch (columnIndex) { case 0: step.clearNoteNumber(); break; case 1: step.clearInstrumentNumber(); break; case 2: step.clearVolume(); break; default: { int effectColumnIndex = columnIndex - 3; int effectNumber = effectColumnIndex / 2; if (effectColumnIndex % 2) { // Effect value column. step.clearEffectValue(effectNumber); } else { // Effect ID number. step.clearEffectId(effectNumber); } break; } } trackIndex += (++columnIndex / Step::N_COLUMN); columnIndex %= Step::N_COLUMN; } } return true; } bool ShrinkPatternCommand::undo() { try { command_utils::restorePattern(mod_.lock()->getSong(song_), prevCells_, bTrack_, bCol_, order_, bStep_); return true; } catch (...) { return false; } } BambooTracker-0.6.5/BambooTracker/command/pattern/shrink_pattern_command.hpp000066400000000000000000000012031476276175200273220ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "module.hpp" #include "vector_2d.hpp" class ShrinkPatternCommand final : public AbstractCommand { public: ShrinkPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginColumn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_, bTrack_, bCol_, order_, bStep_; int eStep_; Vector2d prevCells_; }; BambooTracker-0.6.5/BambooTracker/command/pattern/transpose_note_in_pattern_command.cpp000066400000000000000000000033001476276175200315500ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2020 Rerrah * SPDX-License-Identifier: MIT */ #include "transpose_note_in_pattern_command.hpp" #include "pattern_command_utils.hpp" #include "note.hpp" #include "utils.hpp" TransposeNoteInPatternCommand::TransposeNoteInPatternCommand( std::weak_ptr mod, int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep, int semitone) : AbstractCommand(CommandId::TransposeNoteInPattern), mod_(mod), song_(songNum), bTrack_(beginTrack), order_(beginOrder), bStep_(beginStep), eTrack_(endTrack), eStep_(endStep), semitone_(semitone) { auto& sng = mod.lock()->getSong(songNum); for (int step = beginStep; step <= endStep; ++step) { for (int track = beginTrack; track <= endTrack; ++track) { Step& st = command_utils::getStep(sng, track, beginOrder, step); if (st.hasGeneralNote()) prevKeys_.push_back(st.getNoteNumber()); } } } bool TransposeNoteInPatternCommand::redo() { auto& sng = mod_.lock()->getSong(song_); for (int step = bStep_; step <= eStep_; ++step) { for (int track = bTrack_; track <= eTrack_; ++track) { Step& st = command_utils::getStep(sng, track, order_, step); int n = st.getNoteNumber(); if (st.hasGeneralNote()) { st.setNoteNumber(utils::clamp(n + semitone_, 0, Note::NOTE_NUMBER_RANGE - 1)); } } } return true; } bool TransposeNoteInPatternCommand::undo() { auto& sng = mod_.lock()->getSong(song_); size_t i = 0; for (int step = bStep_; step <= eStep_; ++step) { for (int track = bTrack_; track <= eTrack_; ++track) { Step& st = command_utils::getStep(sng, track, order_, step); if (st.hasGeneralNote()) st.setNoteNumber(prevKeys_.at(i++)); } } return true; } BambooTracker-0.6.5/BambooTracker/command/pattern/transpose_note_in_pattern_command.hpp000066400000000000000000000012011476276175200315530ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2020 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include #include #include "../abstract_command.hpp" #include "module.hpp" class TransposeNoteInPatternCommand final : public AbstractCommand { public: TransposeNoteInPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep, int semitone); bool redo() override; bool undo() override; private: std::weak_ptr mod_; int song_; int bTrack_, order_, bStep_; int eTrack_, eStep_; int semitone_; std::vector prevKeys_; }; BambooTracker-0.6.5/BambooTracker/configuration.cpp000066400000000000000000000541351476276175200223540ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "configuration.hpp" #include #include #include "jamming.hpp" #include "chip/real_chip_interface.hpp" #include "chip/resampler.hpp" namespace { const std::unordered_map KEY_MAP_QWERTY = { {u8"Z", JamKey::LowC}, {u8"S", JamKey::LowCS}, {u8"X", JamKey::LowD}, {u8"D", JamKey::LowDS}, {u8"C", JamKey::LowE}, {u8"V", JamKey::LowF}, {u8"G", JamKey::LowFS}, {u8"B", JamKey::LowG}, {u8"H", JamKey::LowGS}, {u8"N", JamKey::LowA}, {u8"J", JamKey::LowAS}, {u8"M", JamKey::LowB}, {u8",", JamKey::LowC2}, {u8"L", JamKey::LowCS2}, {u8".", JamKey::LowD2}, {u8"Q", JamKey::HighC}, {u8"2", JamKey::HighCS}, {u8"W", JamKey::HighD}, {u8"3", JamKey::HighDS}, {u8"E", JamKey::HighE}, {u8"R", JamKey::HighF}, {u8"5", JamKey::HighFS}, {u8"T", JamKey::HighG}, {u8"6", JamKey::HighGS}, {u8"Y", JamKey::HighA}, {u8"7", JamKey::HighAS}, {u8"U", JamKey::HighB}, {u8"I", JamKey::HighC2}, {u8"9", JamKey::HighCS2}, {u8"O", JamKey::HighD2}, }; const std::unordered_map KEY_MAP_QWERTZ = { {u8"Y", JamKey::LowC}, {u8"S", JamKey::LowCS}, {u8"X", JamKey::LowD}, {u8"D", JamKey::LowDS}, {u8"C", JamKey::LowE}, {u8"V", JamKey::LowF}, {u8"G", JamKey::LowFS}, {u8"B", JamKey::LowG}, {u8"H", JamKey::LowGS}, {u8"N", JamKey::LowA}, {u8"J", JamKey::LowAS}, {u8"M", JamKey::LowB}, {u8",", JamKey::LowC2}, {u8"L", JamKey::LowCS2}, {u8".", JamKey::LowD2}, {u8"Q", JamKey::HighC}, {u8"2", JamKey::HighCS}, {u8"W", JamKey::HighD}, {u8"3", JamKey::HighDS}, {u8"E", JamKey::HighE}, {u8"R", JamKey::HighF}, {u8"5", JamKey::HighFS}, {u8"T", JamKey::HighG}, {u8"6", JamKey::HighGS}, {u8"Z", JamKey::HighA}, {u8"7", JamKey::HighAS}, {u8"U", JamKey::HighB}, {u8"I", JamKey::HighC2}, {u8"9", JamKey::HighCS2}, {u8"O", JamKey::HighD2}, }; const std::unordered_map KEY_MAP_AZERTY = { {u8"W", JamKey::LowC}, {u8"S", JamKey::LowCS}, {u8"X", JamKey::LowD}, {u8"D", JamKey::LowDS}, {u8"C", JamKey::LowE}, {u8"V", JamKey::LowF}, {u8"G", JamKey::LowFS}, {u8"B", JamKey::LowG}, {u8"H", JamKey::LowGS}, {u8"N", JamKey::LowA}, {u8"J", JamKey::LowAS}, {u8",", JamKey::LowB}, {u8";", JamKey::LowC2}, {u8"L", JamKey::LowCS2}, {u8".", JamKey::LowD2}, {u8"A", JamKey::HighC}, {u8"É", JamKey::HighCS}, //é - \xc9 {u8"Z", JamKey::HighD}, {u8"\"", JamKey::HighDS}, {u8"E", JamKey::HighE}, {u8"R", JamKey::HighF}, {u8"(", JamKey::HighFS}, {u8"T", JamKey::HighG}, {u8"-", JamKey::HighGS}, {u8"Y", JamKey::HighA}, {u8"È", JamKey::HighAS}, //è - \xc8 {u8"U", JamKey::HighB}, {u8"I", JamKey::HighC2}, {u8"Ç", JamKey::HighCS2}, //ç - \xc7 {u8"O", JamKey::HighD2}, }; } Configuration::Configuration() { // Internal // followMode_ = true; workDir_ = ""; instOpenFormat_ = 0; bankOpenFormat_ = 0; instMask_ = false; volMask_ = true; visibleToolbar_ = true; visibleStatusBar_ = true; visibleWaveView_ = true; pasteMode_ = PasteMode::Cursor; // Mainwindow state mainW_ = 930; mainH_= 780; mainMax_ = false; mainX_ = -1; // Dummy mainY_ = -1; // Dummy mainVSplit_ = -1; // Dummy // Instrument editor state instFMW_ = 570; instFMH_ = 750; instSSGW_ = 510; instSSGH_ = 390; instADPCMW_ = 510; instADPCMH_ = 430; instKitW_ = 590; instKitH_ = 430; instKitHSplit_ = -1; // Dummy // Toolbar state mainTb_.setPosition(ToolbarPosition::TopPosition); mainTb_.setNumber(0); mainTb_.setBreakBefore(false); mainTb_.setX(-1); // Dummy mainTb_.setY(-1); // Dummy subTb_.setPosition(ToolbarPosition::TopPosition); subTb_.setNumber(1); subTb_.setBreakBefore(false); subTb_.setX(-1); // Dummy subTb_.setY(-1); // Dummy // General // // General settings warpCursor_ = true; warpAcrossOrders_ = true; showRowNumHex_ = true; showPrevNextOrders_ = true; backupModules_ = true; dontSelectOnDoubleClick_ = false; reverseFMVolumeOrder_ = true; moveCursorToRight_ = false; retrieveChannelState_ = false; enableTranslation_ = true; showFMDetuneSigned_ = false; fill00ToEffectValue_ = true; moveCursorHScroll_ = true; overwriteUnusedUnedited_ = false; writeOnlyUsedSamples_ = false; reflectInstNumChange_ = false; fixJamVol_ = true; muteHiddenTracks_ = true; restoreTrackVis_ = false; overflowPaste_ = false; // Edit settings pageJumpLength_ = 4; editableStep_ = 1; keyRepetision_ = true; // Wave view waveViewFps_ = 30; // Note names notationSys_ = NoteNotationSystem::ENGLISH; // Keys shortcuts_ = { { ShortcutAction::KeyOff, u8"-" }, { ShortcutAction::KeyCut, u8"" }, { ShortcutAction::OctaveUp, u8"Num+*" }, { ShortcutAction::OctaveDown, u8"Num+/" }, { ShortcutAction::EchoBuffer, u8"^" }, { ShortcutAction::PlayAndStop, u8"Return" }, { ShortcutAction::Play, u8"" }, { ShortcutAction::PlayFromStart, u8"F5" }, { ShortcutAction::PlayPattern, u8"F6" }, { ShortcutAction::PlayFromCursor, u8"F7" }, { ShortcutAction::PlayFromMarker, u8"Ctrl+F7" }, { ShortcutAction::PlayStep, u8"Ctrl+Return" }, { ShortcutAction::Stop, u8"F8" }, { ShortcutAction::FocusOnPattern, u8"F2" }, { ShortcutAction::FocusOnOrder, u8"F3" }, { ShortcutAction::FocusOnInstrument, u8"F4" }, { ShortcutAction::ToggleEditJam, u8"Space" }, { ShortcutAction::SetMarker, u8"Ctrl+B" }, { ShortcutAction::PasteMix, u8"Ctrl+M" }, { ShortcutAction::PasteOverwrite, u8"" }, { ShortcutAction::PasteInsert, u8"" }, { ShortcutAction::SelectAll, u8"Ctrl+A" }, { ShortcutAction::Deselect, u8"Esc" }, { ShortcutAction::SelectRow, u8"" }, { ShortcutAction::SelectColumn, u8"" }, { ShortcutAction::SelectPattern, u8"" }, { ShortcutAction::SelectOrder, u8"" }, { ShortcutAction::GoToStep, u8"Alt+G" }, { ShortcutAction::ToggleTrack, u8"Alt+F9" }, { ShortcutAction::SoloTrack, u8"Alt+F10" }, { ShortcutAction::Interpolate, u8"Ctrl+G" }, { ShortcutAction::Reverse, u8"Ctrl+R" }, { ShortcutAction::GoToPrevOrder, u8"Ctrl+Left" }, { ShortcutAction::GoToNextOrder, u8"Ctrl+Right" }, { ShortcutAction::ToggleBookmark, u8"Ctrl+K" }, { ShortcutAction::PrevBookmark, u8"Ctrl+PgUp" }, { ShortcutAction::NextBookmark, u8"Ctrl+PgDown" }, { ShortcutAction::DecreaseNote, u8"Ctrl+F1" }, { ShortcutAction::IncreaseNote, u8"Ctrl+F2" }, { ShortcutAction::DecreaseOctave, u8"Ctrl+F3" }, { ShortcutAction::IncreaseOctave, u8"Ctrl+F4" }, { ShortcutAction::PrevInstrument, u8"Alt+Left" }, { ShortcutAction::NextInstrument, u8"Alt+Right" }, { ShortcutAction::MaskInstrument, u8"" }, { ShortcutAction::MaskVolume, u8"" }, { ShortcutAction::EditInstrument, u8"Ctrl+I" }, { ShortcutAction::FollowMode, u8"ScrollLock" }, { ShortcutAction::DuplicateOrder, u8"Ctrl+D" }, { ShortcutAction::ClonePatterns, u8"Alt+D" }, { ShortcutAction::CloneOrder, u8"" }, { ShortcutAction::ReplaceInstrument, u8"Alt+S" }, { ShortcutAction::ExpandPattern, u8"" }, { ShortcutAction::ShrinkPattern, u8"" }, { ShortcutAction::FineDecreaseValues, u8"Shift+F1" }, { ShortcutAction::FineIncreaseValues, u8"Shift+F2" }, { ShortcutAction::CoarseDecreaseValues, u8"Shift+F3" }, { ShortcutAction::CoarseIncreaseValuse, u8"Shift+F4" }, { ShortcutAction::ExpandEffect, u8"Alt+L" }, { ShortcutAction::ShrinkEffect, u8"Alt+K" }, { ShortcutAction::PrevHighlighted, u8"Ctrl+Up" }, { ShortcutAction::NextHighlighted, u8"Ctrl+Down" }, { ShortcutAction::IncreasePatternSize, u8"" }, { ShortcutAction::DecreasePatternSize, u8"" }, { ShortcutAction::IncreaseEditStep, u8"" }, { ShortcutAction::DecreaseEditStep, u8"" }, { ShortcutAction::DisplayEffectList, u8"F1" }, { ShortcutAction::PreviousSong, u8"" }, { ShortcutAction::NextSong, u8"" }, { ShortcutAction::JamVolumeUp, u8"" }, { ShortcutAction::JamVolumeDown, u8"" } }; noteEntryLayout_ = KeyboardLayout::QWERTY; // Sound // sndAPI_ = u8""; sndDevice_ = u8""; realChip_ = RealChipInterfaceType::NONE; emulator_ = 1; sampleRate_ = 44100; bufferLength_ = 40; resamplerType_ = chip::ResamplerType::BlipBuf; isImmediateWriteMode_ = false; // Midi // midiEnabled_ = false; midiAPI_ = u8""; midiInPort_ = u8""; // Mixer // mixerVolumeMaster_ = 100; mixerVolumeFM_ = 0; mixerVolumeSSG_ = 0; // Input // fmEnvelopeTexts_ = { { "PMD", std::vector({ FMEnvelopeTextType::Skip, FMEnvelopeTextType::AL, FMEnvelopeTextType::FB, FMEnvelopeTextType::AR1, FMEnvelopeTextType::DR1, FMEnvelopeTextType::SR1, FMEnvelopeTextType::RR1, FMEnvelopeTextType::SL1, FMEnvelopeTextType::TL1, FMEnvelopeTextType::KS1, FMEnvelopeTextType::ML1, FMEnvelopeTextType::DT1, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR2, FMEnvelopeTextType::DR2, FMEnvelopeTextType::SR2, FMEnvelopeTextType::RR2, FMEnvelopeTextType::SL2, FMEnvelopeTextType::TL2, FMEnvelopeTextType::KS2, FMEnvelopeTextType::ML2, FMEnvelopeTextType::DT2, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR3, FMEnvelopeTextType::DR3, FMEnvelopeTextType::SR3, FMEnvelopeTextType::RR3, FMEnvelopeTextType::SL3, FMEnvelopeTextType::TL3, FMEnvelopeTextType::KS3, FMEnvelopeTextType::ML3, FMEnvelopeTextType::DT3, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR4, FMEnvelopeTextType::DR4, FMEnvelopeTextType::SR4, FMEnvelopeTextType::RR4, FMEnvelopeTextType::SL4, FMEnvelopeTextType::TL4, FMEnvelopeTextType::KS4, FMEnvelopeTextType::ML4, FMEnvelopeTextType::DT4, FMEnvelopeTextType::Skip }) }, { "FMP", std::vector({ FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR1, FMEnvelopeTextType::DR1, FMEnvelopeTextType::SR1, FMEnvelopeTextType::RR1, FMEnvelopeTextType::SL1, FMEnvelopeTextType::TL1, FMEnvelopeTextType::KS1, FMEnvelopeTextType::ML1, FMEnvelopeTextType::DT1, FMEnvelopeTextType::AR2, FMEnvelopeTextType::DR2, FMEnvelopeTextType::SR2, FMEnvelopeTextType::RR2, FMEnvelopeTextType::SL2, FMEnvelopeTextType::TL2, FMEnvelopeTextType::KS2, FMEnvelopeTextType::ML2, FMEnvelopeTextType::DT2, FMEnvelopeTextType::AR3, FMEnvelopeTextType::DR3, FMEnvelopeTextType::SR3, FMEnvelopeTextType::RR3, FMEnvelopeTextType::SL3, FMEnvelopeTextType::TL3, FMEnvelopeTextType::KS3, FMEnvelopeTextType::ML3, FMEnvelopeTextType::DT3, FMEnvelopeTextType::AR4, FMEnvelopeTextType::DR4, FMEnvelopeTextType::SR4, FMEnvelopeTextType::RR4, FMEnvelopeTextType::SL4, FMEnvelopeTextType::TL4, FMEnvelopeTextType::KS4, FMEnvelopeTextType::ML4, FMEnvelopeTextType::DT4, FMEnvelopeTextType::AL, FMEnvelopeTextType::FB }) }, { "FMP7", std::vector({ FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR1, FMEnvelopeTextType::DR1, FMEnvelopeTextType::SR1, FMEnvelopeTextType::RR1, FMEnvelopeTextType::SL1, FMEnvelopeTextType::TL1, FMEnvelopeTextType::KS1, FMEnvelopeTextType::ML1, FMEnvelopeTextType::DT1, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR2, FMEnvelopeTextType::DR2, FMEnvelopeTextType::SR2, FMEnvelopeTextType::RR2, FMEnvelopeTextType::SL2, FMEnvelopeTextType::TL2, FMEnvelopeTextType::KS2, FMEnvelopeTextType::ML2, FMEnvelopeTextType::DT2, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR3, FMEnvelopeTextType::DR3, FMEnvelopeTextType::SR3, FMEnvelopeTextType::RR3, FMEnvelopeTextType::SL3, FMEnvelopeTextType::TL3, FMEnvelopeTextType::KS3, FMEnvelopeTextType::ML3, FMEnvelopeTextType::DT3, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR4, FMEnvelopeTextType::DR4, FMEnvelopeTextType::SR4, FMEnvelopeTextType::RR4, FMEnvelopeTextType::SL4, FMEnvelopeTextType::TL4, FMEnvelopeTextType::KS4, FMEnvelopeTextType::ML4, FMEnvelopeTextType::DT4, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AL, FMEnvelopeTextType::FB }) }, { "VOPM", std::vector({ // Number FMEnvelopeTextType::Skip, // LFO FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, // CH FMEnvelopeTextType::Skip, FMEnvelopeTextType::FB, FMEnvelopeTextType::AL, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, // Op FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR1, FMEnvelopeTextType::DR1, FMEnvelopeTextType::SR1, FMEnvelopeTextType::RR1, FMEnvelopeTextType::SL1, FMEnvelopeTextType::TL1, FMEnvelopeTextType::KS1, FMEnvelopeTextType::ML1, FMEnvelopeTextType::DT1, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR2, FMEnvelopeTextType::DR2, FMEnvelopeTextType::SR2, FMEnvelopeTextType::RR2, FMEnvelopeTextType::SL2, FMEnvelopeTextType::TL2, FMEnvelopeTextType::KS2, FMEnvelopeTextType::ML2, FMEnvelopeTextType::DT2, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR3, FMEnvelopeTextType::DR3, FMEnvelopeTextType::SR3, FMEnvelopeTextType::RR3, FMEnvelopeTextType::SL3, FMEnvelopeTextType::TL3, FMEnvelopeTextType::KS3, FMEnvelopeTextType::ML3, FMEnvelopeTextType::DT3, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR4, FMEnvelopeTextType::DR4, FMEnvelopeTextType::SR4, FMEnvelopeTextType::RR4, FMEnvelopeTextType::SL4, FMEnvelopeTextType::TL4, FMEnvelopeTextType::KS4, FMEnvelopeTextType::ML4, FMEnvelopeTextType::DT4, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip }) }, { "NRTDRV", // For VOICE_MODE=0 std::vector({ FMEnvelopeTextType::Skip, FMEnvelopeTextType::AL, FMEnvelopeTextType::FB, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR1, FMEnvelopeTextType::DR1, FMEnvelopeTextType::SR1, FMEnvelopeTextType::RR1, FMEnvelopeTextType::SL1, FMEnvelopeTextType::TL1, FMEnvelopeTextType::KS1, FMEnvelopeTextType::ML1, FMEnvelopeTextType::DT1, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR2, FMEnvelopeTextType::DR2, FMEnvelopeTextType::SR2, FMEnvelopeTextType::RR2, FMEnvelopeTextType::SL2, FMEnvelopeTextType::TL2, FMEnvelopeTextType::KS2, FMEnvelopeTextType::ML2, FMEnvelopeTextType::DT2, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR3, FMEnvelopeTextType::DR3, FMEnvelopeTextType::SR3, FMEnvelopeTextType::RR3, FMEnvelopeTextType::SL3, FMEnvelopeTextType::TL3, FMEnvelopeTextType::KS3, FMEnvelopeTextType::ML3, FMEnvelopeTextType::DT3, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR4, FMEnvelopeTextType::DR4, FMEnvelopeTextType::SR4, FMEnvelopeTextType::RR4, FMEnvelopeTextType::SL4, FMEnvelopeTextType::TL4, FMEnvelopeTextType::KS4, FMEnvelopeTextType::ML4, FMEnvelopeTextType::DT4, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip }) }, { "MXDRV", std::vector({ FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR1, FMEnvelopeTextType::DR1, FMEnvelopeTextType::SR1, FMEnvelopeTextType::RR1, FMEnvelopeTextType::SL1, FMEnvelopeTextType::TL1, FMEnvelopeTextType::KS1, FMEnvelopeTextType::ML1, FMEnvelopeTextType::DT1, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR2, FMEnvelopeTextType::DR2, FMEnvelopeTextType::SR2, FMEnvelopeTextType::RR2, FMEnvelopeTextType::SL2, FMEnvelopeTextType::TL2, FMEnvelopeTextType::KS2, FMEnvelopeTextType::ML2, FMEnvelopeTextType::DT2, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR3, FMEnvelopeTextType::DR3, FMEnvelopeTextType::SR3, FMEnvelopeTextType::RR3, FMEnvelopeTextType::SL3, FMEnvelopeTextType::TL3, FMEnvelopeTextType::KS3, FMEnvelopeTextType::ML3, FMEnvelopeTextType::DT3, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR4, FMEnvelopeTextType::DR4, FMEnvelopeTextType::SR4, FMEnvelopeTextType::RR4, FMEnvelopeTextType::SL4, FMEnvelopeTextType::TL4, FMEnvelopeTextType::KS4, FMEnvelopeTextType::ML4, FMEnvelopeTextType::DT4, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AL, FMEnvelopeTextType::FB, FMEnvelopeTextType::Skip }) }, { "MMLDRV", std::vector({ FMEnvelopeTextType::Skip, FMEnvelopeTextType::AL, FMEnvelopeTextType::FB, FMEnvelopeTextType::AR1, FMEnvelopeTextType::DR1, FMEnvelopeTextType::SR1, FMEnvelopeTextType::RR1, FMEnvelopeTextType::SL1, FMEnvelopeTextType::TL1, FMEnvelopeTextType::KS1, FMEnvelopeTextType::ML1, FMEnvelopeTextType::DT1, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR2, FMEnvelopeTextType::DR2, FMEnvelopeTextType::SR2, FMEnvelopeTextType::RR2, FMEnvelopeTextType::SL2, FMEnvelopeTextType::TL2, FMEnvelopeTextType::KS2, FMEnvelopeTextType::ML2, FMEnvelopeTextType::DT2, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR3, FMEnvelopeTextType::DR3, FMEnvelopeTextType::SR3, FMEnvelopeTextType::RR3, FMEnvelopeTextType::SL3, FMEnvelopeTextType::TL3, FMEnvelopeTextType::KS3, FMEnvelopeTextType::ML3, FMEnvelopeTextType::DT3, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR4, FMEnvelopeTextType::DR4, FMEnvelopeTextType::SR4, FMEnvelopeTextType::RR4, FMEnvelopeTextType::SL4, FMEnvelopeTextType::TL4, FMEnvelopeTextType::KS4, FMEnvelopeTextType::ML4, FMEnvelopeTextType::DT4, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip }) }, { "MUCOM88", std::vector({ FMEnvelopeTextType::Skip, FMEnvelopeTextType::FB, FMEnvelopeTextType::AL, FMEnvelopeTextType::AR1, FMEnvelopeTextType::DR1, FMEnvelopeTextType::SR1, FMEnvelopeTextType::RR1, FMEnvelopeTextType::SL1, FMEnvelopeTextType::TL1, FMEnvelopeTextType::KS1, FMEnvelopeTextType::ML1, FMEnvelopeTextType::DT1, FMEnvelopeTextType::AR2, FMEnvelopeTextType::DR2, FMEnvelopeTextType::SR2, FMEnvelopeTextType::RR2, FMEnvelopeTextType::SL2, FMEnvelopeTextType::TL2, FMEnvelopeTextType::KS2, FMEnvelopeTextType::ML2, FMEnvelopeTextType::DT2, FMEnvelopeTextType::AR3, FMEnvelopeTextType::DR3, FMEnvelopeTextType::SR3, FMEnvelopeTextType::RR3, FMEnvelopeTextType::SL3, FMEnvelopeTextType::TL3, FMEnvelopeTextType::KS3, FMEnvelopeTextType::ML3, FMEnvelopeTextType::DT3, FMEnvelopeTextType::AR4, FMEnvelopeTextType::DR4, FMEnvelopeTextType::SR4, FMEnvelopeTextType::RR4, FMEnvelopeTextType::SL4, FMEnvelopeTextType::TL4, FMEnvelopeTextType::KS4, FMEnvelopeTextType::ML4, FMEnvelopeTextType::DT4 }) } }; // Layouts mappingCustom_ = {}; mappingLayouts = { { KeyboardLayout::Custom, mappingCustom_ }, { KeyboardLayout::QWERTY, KEY_MAP_QWERTY }, { KeyboardLayout::QWERTZ, KEY_MAP_QWERTZ }, { KeyboardLayout::AZERTY, KEY_MAP_AZERTY } }; // Appearance ptnHdFont_ = u8""; ptnRowFont_ = u8""; odrHdFont_ = u8""; odrRowFont_ = u8""; } // Keys void Configuration::setShortcuts(std::unordered_map shortcuts) { // Completment std::set orgActs, newActs, diffActs; std::transform(shortcuts_.cbegin(), shortcuts_.cend(), std::inserter(orgActs, orgActs.end()), [](const auto& pair) { return pair.first; }); std::transform(shortcuts.cbegin(), shortcuts.cend(), std::inserter(newActs, newActs.end()), [](const auto& pair) { return pair.first; }); std::set_difference(orgActs.cbegin(), orgActs.cend(), newActs.cbegin(), newActs.cend(), std::inserter(diffActs, diffActs.end())); for (auto& act : diffActs) { shortcuts[act] = u8""; } shortcuts_ = shortcuts; } void Configuration::setCustomLayoutKeys(const std::unordered_map& mapping) { mappingLayouts[KeyboardLayout::Custom] = mapping; } std::unordered_map Configuration::getCustomLayoutKeys() const { return mappingLayouts.at(KeyboardLayout::Custom); } BambooTracker-0.6.5/BambooTracker/configuration.hpp000066400000000000000000000367021476276175200223610ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include #include #include "enum_hash.hpp" enum class JamKey : int; enum class RealChipInterfaceType : int; enum class FMEnvelopeTextType : int { Skip, AL, FB, AR1, DR1, SR1, RR1, SL1, TL1, KS1, ML1, DT1, AR2, DR2, SR2, RR2, SL2, TL2, KS2, ML2, DT2, AR3, DR3, SR3, RR3, SL3, TL3, KS3, ML3, DT3, AR4, DR4, SR4, RR4, SL4, TL4, KS4, ML4, DT4 }; struct FMEnvelopeText { std::string name; std::vector texts; }; enum class NoteNotationSystem : int { ENGLISH = 0, GERMAN = 1 }; namespace chip { enum class ResamplerType : int; } class Configuration { public: Configuration(); // Internal // public: void setFollowMode(bool enabled) { followMode_ = enabled; } bool getFollowMode() const { return followMode_; } void setWorkingDirectory(const std::string& path) { workDir_ = path; } std::string getWorkingDirectory() const { return workDir_; } void setInstrumentOpenFormat(int i) { instOpenFormat_ = i; } int getInstrumentOpenFormat() const { return instOpenFormat_; } void setBankOpenFormat(int i) { bankOpenFormat_ = i; } int getBankOpenFormat() const { return bankOpenFormat_; } void setInstrumentMask(bool enabled) { instMask_ = enabled; } bool getInstrumentMask() const { return instMask_; } void setVolumeMask(bool enabled) { volMask_ = enabled; } bool getVolumeMask() const { return volMask_; } void setVisibleToolbar(bool visible) { visibleToolbar_ = visible; } bool getVisibleToolbar() const { return visibleToolbar_; } void setVisibleStatusBar(bool visible) { visibleStatusBar_ = visible; } bool getVisibleStatusBar() const { return visibleStatusBar_; } void setVisibleWaveView(bool visible) { visibleWaveView_ = visible; } bool getVisibleWaveView() const { return visibleWaveView_; } enum class PasteMode : int { Cursor, Selection, Fill }; void setPasteMode(PasteMode mode) { pasteMode_ = mode; } PasteMode getPasteMode() const { return pasteMode_; } private: bool followMode_; std::string workDir_; int instOpenFormat_, bankOpenFormat_; bool instMask_, volMask_; bool visibleToolbar_, visibleStatusBar_, visibleWaveView_; PasteMode pasteMode_; // Mainwindow state public: void setMainWindowWidth(int w) { mainW_ = w; } int getMainWindowWidth() const { return mainW_; } void setMainWindowHeight(int h) { mainH_ = h; } int getMainWindowHeight() const { return mainH_; } void setMainWindowMaximized(bool isMax) { mainMax_ = isMax; } bool getMainWindowMaximized() const { return mainMax_; } void setMainWindowX(int x) { mainX_ = x; } int getMainWindowX() const { return mainX_; } void setMainWindowY(int y) { mainY_ = y; } int getMainWindowY() const { return mainY_; } void setMainWindowVerticalSplit(int y) { mainVSplit_ = y; } int getMainWindowVerticalSplit() const { return mainVSplit_; } private: int mainW_, mainH_; bool mainMax_; int mainX_, mainY_; int mainVSplit_; // Instrument editor state public: void setInstrumentFMWindowWidth(int w) { instFMW_ = w; } int getInstrumentFMWindowWidth() const { return instFMW_; } void setInstrumentFMWindowHeight(int h) { instFMH_ = h; } int getInstrumentFMWindowHeight() const { return instFMH_; } void setInstrumentSSGWindowWidth(int w) { instSSGW_ = w; } int getInstrumentSSGWindowWidth() const { return instSSGW_; } void setInstrumentSSGWindowHeight(int h) { instSSGH_ = h; } int getInstrumentSSGWindowHeight() const { return instSSGH_; } void setInstrumentADPCMWindowWidth(int w) { instADPCMW_ = w; } int getInstrumentADPCMWindowWidth() const { return instADPCMW_; } void setInstrumentADPCMWindowHeight(int h) { instADPCMH_ = h; } int getInstrumentADPCMWindowHeight() const { return instADPCMH_; } void setInstrumentDrumkitWindowWidth(int w) { instKitW_ = w; } int getInstrumentDrumkitWindowWidth() const { return instKitW_; } void setInstrumentDrumkitWindowHeight(int h) { instKitH_ = h; } int getInstrumentDrumkitWindowHeight() const { return instKitH_; } void setInstrumentDrumkitWindowHorizontalSplit(int y) { instKitHSplit_ = y; } int getInstrumentDrumkitWindowHorizontalSplit() const { return instKitHSplit_; } private: int instFMW_, instFMH_; int instSSGW_, instSSGH_; int instADPCMW_, instADPCMH_; int instKitW_, instKitH_; int instKitHSplit_; // Toolbar state public: class ToolbarConfiguration { public: enum class Position : int { TopPosition = 0, BottomPosition, LeftPosition, RightPosition, FloatPorition }; void setPosition(Position pos) { pos_ = pos; } Position getPosition() const { return pos_; } void setNumber(int n) { num_ = n; } int getNumber() const { return num_; } void setBreakBefore(bool enabled) { hasBreakBefore_ = enabled; } bool hasBreakBefore() const { return hasBreakBefore_; } void setX(int x) { x_ = x; } int getX() const { return x_; } void setY(int y) { y_ = y; } int getY() const { return y_; } private: Position pos_; int num_; bool hasBreakBefore_; int x_, y_; }; using ToolbarPosition = ToolbarConfiguration::Position; ToolbarConfiguration& getMainToolbarConfiguration() { return mainTb_; } ToolbarConfiguration& getSubToolbarConfiguration() { return subTb_; } private: ToolbarConfiguration mainTb_, subTb_; // General // // General settings public: void setWarpCursor(bool enabled) { warpCursor_ = enabled; } bool getWarpCursor() const { return warpCursor_; } void setWarpAcrossOrders(bool enabled) { warpAcrossOrders_ = enabled; } bool getWarpAcrossOrders() const { return warpAcrossOrders_; } void setShowRowNumberInHex(bool enabled) { showRowNumHex_ = enabled; } bool getShowRowNumberInHex() const { return showRowNumHex_; } void setShowPreviousNextOrders(bool enabled) { showPrevNextOrders_ = enabled; } bool getShowPreviousNextOrders() const { return showPrevNextOrders_; } void setBackupModules(bool enabled) { backupModules_ = enabled; } bool getBackupModules() const { return backupModules_; } void setDontSelectOnDoubleClick(bool enabled) { dontSelectOnDoubleClick_ = enabled; } bool getDontSelectOnDoubleClick() const { return dontSelectOnDoubleClick_; } void setReverseFMVolumeOrder(bool enabled) { reverseFMVolumeOrder_= enabled; } bool getReverseFMVolumeOrder() const { return reverseFMVolumeOrder_; } void setMoveCursorToRight(bool enabled) { moveCursorToRight_ = enabled; } bool getMoveCursorToRight() const { return moveCursorToRight_; } void setRetrieveChannelState(bool enabled) { retrieveChannelState_ = enabled; } bool getRetrieveChannelState() const { return retrieveChannelState_; } void setEnableTranslation(bool enabled) { enableTranslation_ = enabled; } bool getEnableTranslation() const { return enableTranslation_; } void setShowFMDetuneAsSigned(bool enabled) { showFMDetuneSigned_ = enabled; } bool getShowFMDetuneAsSigned() const { return showFMDetuneSigned_; } void setFill00ToEffectValue(bool enabled) { fill00ToEffectValue_ = enabled; } bool getFill00ToEffectValue() const { return fill00ToEffectValue_; } void setMoveCursorByHorizontalScroll(bool enabled) { moveCursorHScroll_ = enabled; } bool getMoveCursorByHorizontalScroll() const { return moveCursorHScroll_; } void setOverwriteUnusedUneditedPropety(bool enabled) { overwriteUnusedUnedited_ = enabled; } bool getOverwriteUnusedUneditedPropety() const { return overwriteUnusedUnedited_; } void setWriteOnlyUsedSamples(bool enabled) { writeOnlyUsedSamples_ = enabled; } bool getWriteOnlyUsedSamples() const { return writeOnlyUsedSamples_; } void setReflectInstrumentNumberChange(bool enabled) { reflectInstNumChange_ = enabled; } bool getReflectInstrumentNumberChange() const { return reflectInstNumChange_; } void setFixJammingVolume(bool enabled) { fixJamVol_ = enabled; } bool getFixJammingVolume() const { return fixJamVol_; } void setMuteHiddenTracks(bool enabled) { muteHiddenTracks_ = enabled; } bool getMuteHiddenTracks() const { return muteHiddenTracks_; } void setRestoreTrackVisibility(bool enabled) { restoreTrackVis_ = enabled; } bool getRestoreTrackVisibility() const { return restoreTrackVis_; } void setOverflowPaste(bool enabled) { overflowPaste_ = enabled; } bool getOverflowPaste() const { return overflowPaste_; } private: bool warpCursor_, warpAcrossOrders_, showRowNumHex_, showPrevNextOrders_, backupModules_; bool dontSelectOnDoubleClick_, reverseFMVolumeOrder_, moveCursorToRight_, retrieveChannelState_; bool enableTranslation_, showFMDetuneSigned_, fill00ToEffectValue_, moveCursorHScroll_; bool overwriteUnusedUnedited_, writeOnlyUsedSamples_, reflectInstNumChange_, fixJamVol_; bool muteHiddenTracks_, restoreTrackVis_, overflowPaste_; // Edit settings public: void setPageJumpLength(size_t length) { pageJumpLength_ = length; } size_t getPageJumpLength() const { return pageJumpLength_; } void setEditableStep(size_t step) { editableStep_ = step; } size_t getEditableStep() const { return editableStep_; } void setKeyRepetition(bool enabled) { keyRepetision_ = enabled; } bool getKeyRepetition() const { return keyRepetision_; } private: size_t pageJumpLength_, editableStep_; bool keyRepetision_; // Wave view public: void setWaveViewFrameRate(int rate) { waveViewFps_ = rate; } int getWaveViewFrameRate() const { return waveViewFps_; } private: int waveViewFps_; // Note names public: void setNotationSystem(NoteNotationSystem sys) { notationSys_ = sys; } NoteNotationSystem getNotationSystem() const { return notationSys_; } private: NoteNotationSystem notationSys_; // Keys public: enum class ShortcutAction : int { KeyOff, OctaveUp, OctaveDown, EchoBuffer, PlayAndStop, Play, PlayFromStart, PlayPattern, PlayFromCursor, PlayFromMarker, PlayStep, Stop, FocusOnPattern, FocusOnOrder, FocusOnInstrument, ToggleEditJam, SetMarker, PasteMix, PasteOverwrite, PasteInsert, SelectAll, Deselect, SelectRow, SelectColumn, SelectPattern, SelectOrder, GoToStep, ToggleTrack, SoloTrack, Interpolate, Reverse, GoToPrevOrder, GoToNextOrder, ToggleBookmark, PrevBookmark, NextBookmark, DecreaseNote, IncreaseNote, DecreaseOctave, IncreaseOctave, PrevInstrument, NextInstrument, MaskInstrument, MaskVolume, EditInstrument, FollowMode, DuplicateOrder, ClonePatterns, CloneOrder, ReplaceInstrument, ExpandPattern, ShrinkPattern, FineDecreaseValues, FineIncreaseValues, CoarseDecreaseValues, CoarseIncreaseValuse, ExpandEffect, ShrinkEffect, PrevHighlighted, NextHighlighted, IncreasePatternSize, DecreasePatternSize, IncreaseEditStep, DecreaseEditStep, DisplayEffectList, PreviousSong, NextSong, JamVolumeUp, JamVolumeDown, KeyCut }; void setShortcuts(std::unordered_map shortcuts); std::unordered_map getShortcuts() const { return shortcuts_; } enum class KeyboardLayout : int { // at the top, so new layouts can easily be added in after it // and it's always easy to find no matter how many layouts we add Custom = 0, QWERTY, QWERTZ, AZERTY }; std::unordered_map> mappingLayouts; void setNoteEntryLayout(KeyboardLayout layout) { noteEntryLayout_ = layout; } KeyboardLayout getNoteEntryLayout() const { return noteEntryLayout_; } void setCustomLayoutKeys(const std::unordered_map& mapping); std::unordered_map getCustomLayoutKeys() const; private: std::unordered_map shortcuts_; KeyboardLayout noteEntryLayout_; std::unordered_map mappingCustom_; // Sound // public: void setSoundAPI(const std::string& api) { sndAPI_ = api; } std::string getSoundAPI() const { return sndAPI_; } void setSoundDevice(const std::string& device) { sndDevice_ = device; } std::string getSoundDevice() const { return sndDevice_; } void setRealChipInterface(RealChipInterfaceType type) { realChip_ = type; } RealChipInterfaceType getRealChipInterface() const { return realChip_; } void setEmulator(int emulator) { emulator_ = emulator; } int getEmulator() const { return emulator_; } void setSampleRate(uint32_t rate) { sampleRate_ = rate; } uint32_t getSampleRate() const { return sampleRate_; } void setBufferLength(size_t length) { bufferLength_ = length; } size_t getBufferLength() const { return bufferLength_; } void setResamplerType(chip::ResamplerType type) { resamplerType_ = type; } chip::ResamplerType getResamplerType() const { return resamplerType_; } void setImmediateWriteModeEnabled(bool enabled) { isImmediateWriteMode_ = enabled; } bool getImmediateWriteModeEnabled() const { return isImmediateWriteMode_; } private: std::string sndAPI_, sndDevice_; RealChipInterfaceType realChip_; int emulator_; uint32_t sampleRate_; size_t bufferLength_; chip::ResamplerType resamplerType_; bool isImmediateWriteMode_; // Midi // public: void setMidiEnabled(const bool enabled) { midiEnabled_ = enabled; } bool getMidiEnabled() const { return midiEnabled_; } void setMidiAPI(const std::string& api) { midiAPI_ = api; } std::string getMidiAPI() const { return midiAPI_; } void setMidiInputPort(const std::string& port) { midiInPort_ = port; } std::string getMidiInputPort() const { return midiInPort_; } private: bool midiEnabled_; std::string midiAPI_, midiInPort_; // Mixer // public: void setMixerVolumeMaster(int percentage) { mixerVolumeMaster_ = percentage; } int getMixerVolumeMaster() const { return mixerVolumeMaster_; } void setMixerVolumeFM(double dB) { mixerVolumeFM_ = dB; } double getMixerVolumeFM() const { return mixerVolumeFM_; } void setMixerVolumeSSG(double dB) { mixerVolumeSSG_ = dB; } double getMixerVolumeSSG() const { return mixerVolumeSSG_; } private: int mixerVolumeMaster_; double mixerVolumeFM_, mixerVolumeSSG_; // Input // public: void setFMEnvelopeTexts(const std::vector& texts) { fmEnvelopeTexts_ = texts; } std::vector getFMEnvelopeTexts() const { return fmEnvelopeTexts_; } // Appearance // public: void setPatternEditorHeaderFont(const std::string& font) { ptnHdFont_ = font; } std::string getPatternEditorHeaderFont() const { return ptnHdFont_; } void setPatternEditorRowsFont(const std::string& font) { ptnRowFont_ = font; } std::string getPatternEditorRowsFont() const { return ptnRowFont_; } void setOrderListHeaderFont(const std::string& font) { odrHdFont_ = font; } std::string getOrderListHeaderFont() const { return odrHdFont_; } void setOrderListRowsFont(const std::string& font) { odrRowFont_ = font; } std::string getOrderListRowsFont() const { return odrRowFont_; } private: std::string ptnHdFont_, ptnRowFont_, odrHdFont_, odrRowFont_; private: std::vector fmEnvelopeTexts_; }; BambooTracker-0.6.5/BambooTracker/echo_buffer.hpp000066400000000000000000000037301476276175200217540ustar00rootroot00000000000000/* * Copyright (C) 2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include "note.hpp" class EchoBuffer { using DequeType = std::deque; public: using reference = DequeType::reference; using const_reference = DequeType::const_reference; using size_type = DequeType::size_type; reference at(size_type n) { return deque_.at(n); } const_reference at(size_type n) const { return deque_.at(n); } reference operator[](size_type n) { return deque_[n]; } const_reference operator[](size_type n) const { return deque_[n]; } reference latest() { return deque_.front(); } const_reference latest() const { return deque_.front(); } size_type size() const noexcept { return deque_.size(); } void clear() noexcept { deque_.clear(); } void push(const Note& y) { deque_.push_front(y); if (MAX_ <= deque_.size()) deque_.pop_back(); } private: std::deque deque_; static constexpr size_type MAX_ = 4; }; BambooTracker-0.6.5/BambooTracker/enum_hash.hpp000066400000000000000000000007771476276175200214640ustar00rootroot00000000000000#pragma once #if (defined __GNUC__) && (!defined __clang__) #if (__GNUC__ < 6) || ((__GNUC__ == 6) && (__GNUC_MINOR__ < 1)) // Unsupport std::hash with enum types before gcc 6.1. // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60970 #include namespace std { template struct hash { static_assert(is_enum::value, "..."); size_t operator()(T x) const noexcept { using type = typename underlying_type::type; return hash{}(static_cast(x)); } }; } #endif #endif BambooTracker-0.6.5/BambooTracker/format/000077500000000000000000000000001476276175200202615ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/format/wopn_file.c000066400000000000000000000432611476276175200224150ustar00rootroot00000000000000/* * Wohlstand's OPN2 Bank File - a bank format to store OPN2 timbre data and setup * * Copyright (c) 2018 Vitaly Novichkov * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "wopn_file.h" #include #include static const char *wopn2_magic1 = "WOPN2-BANK\0"; static const char *wopn2_magic2 = "WOPN2-B2NK\0"; static const char *opni_magic1 = "WOPN2-INST\0"; static const char *opni_magic2 = "WOPN2-IN2T\0"; static const uint16_t wopn_latest_version = 2; enum { WOPN_INST_SIZE_V1 = 65, WOPN_INST_SIZE_V2 = 69 }; static uint16_t toUint16LE(const uint8_t *arr) { uint16_t num = arr[0]; num |= ((arr[1] << 8) & 0xFF00); return num; } static uint16_t toUint16BE(const uint8_t *arr) { uint16_t num = arr[1]; num |= ((arr[0] << 8) & 0xFF00); return num; } static int16_t toSint16BE(const uint8_t *arr) { int16_t num = *(const int8_t *)(&arr[0]); num *= 1 << 8; num |= arr[1]; return num; } static void fromUint16LE(uint16_t in, uint8_t *arr) { arr[0] = in & 0x00FF; arr[1] = (in >> 8) & 0x00FF; } static void fromUint16BE(uint16_t in, uint8_t *arr) { arr[1] = in & 0x00FF; arr[0] = (in >> 8) & 0x00FF; } static void fromSint16BE(int16_t in, uint8_t *arr) { arr[1] = in & 0x00FF; arr[0] = ((uint16_t)in >> 8) & 0x00FF; } WOPNFile *WOPN_Init(uint16_t melodic_banks, uint16_t percussive_banks) { WOPNFile *file = (WOPNFile*)calloc(1, sizeof(WOPNFile)); if(!file) return NULL; file->banks_count_melodic = (melodic_banks != 0) ? melodic_banks : 1; file->banks_melodic = (WOPNBank*)calloc(file->banks_count_melodic, sizeof(WOPNBank)); if(melodic_banks == 0) { unsigned i; for(i = 0; i < 128; ++i) file->banks_melodic[0].ins[i].inst_flags = WOPN_Ins_IsBlank; } file->banks_count_percussion = (percussive_banks != 0) ? percussive_banks : 1; file->banks_percussive = (WOPNBank*)calloc(file->banks_count_percussion, sizeof(WOPNBank)); if(percussive_banks == 0) { unsigned i; for(i = 0; i < 128; ++i) file->banks_percussive[0].ins[i].inst_flags = WOPN_Ins_IsBlank; } return file; } void WOPN_Free(WOPNFile *file) { if(file) { if(file->banks_melodic) free(file->banks_melodic); if(file->banks_percussive) free(file->banks_percussive); free(file); } } int WOPN_BanksCmp(const WOPNFile *bank1, const WOPNFile *bank2) { int res = 1; res &= (bank1->version == bank2->version); res &= (bank1->lfo_freq == bank2->lfo_freq); res &= (bank1->chip_type == bank2->chip_type); res &= (bank1->volume_model == bank2->volume_model); res &= (bank1->banks_count_melodic == bank2->banks_count_melodic); res &= (bank1->banks_count_percussion == bank2->banks_count_percussion); if(res) { int i; for(i = 0; i < bank1->banks_count_melodic; i++) res &= (memcmp(&bank1->banks_melodic[i], &bank2->banks_melodic[i], sizeof(WOPNBank)) == 0); if(res) { for(i = 0; i < bank1->banks_count_percussion; i++) res &= (memcmp(&bank1->banks_percussive[i], &bank2->banks_percussive[i], sizeof(WOPNBank)) == 0); } } return res; } static void WOPN_parseInstrument(WOPNInstrument *ins, uint8_t *cursor, uint16_t version, uint8_t has_sounding_delays) { int l; strncpy(ins->inst_name, (const char*)cursor, 32); ins->inst_name[32] = '\0'; ins->note_offset = toSint16BE(cursor + 32); ins->midi_velocity_offset = 0; /* TODO: for future version > 2 */ ins->percussion_key_number = cursor[34]; ins->inst_flags = 0; /* TODO: for future version > 2 */ ins->fbalg = cursor[35]; ins->lfosens = cursor[36]; for(l = 0; l < 4; l++) { size_t off = 37 + (size_t)(l) * 7; ins->operators[l].dtfm_30 = cursor[off + 0]; ins->operators[l].level_40 = cursor[off + 1]; ins->operators[l].rsatk_50 = cursor[off + 2]; ins->operators[l].amdecay1_60 = cursor[off + 3]; ins->operators[l].decay2_70 = cursor[off + 4]; ins->operators[l].susrel_80 = cursor[off + 5]; ins->operators[l].ssgeg_90 = cursor[off + 6]; } if((version >= 2) && has_sounding_delays) { ins->delay_on_ms = toUint16BE(cursor + 65); ins->delay_off_ms = toUint16BE(cursor + 67); /* Null delays indicate the blank instrument in version 2 */ if((version < 3) && ins->delay_on_ms == 0 && ins->delay_off_ms == 0) ins->inst_flags |= WOPN_Ins_IsBlank; } } static void WOPN_writeInstrument(WOPNInstrument *ins, uint8_t *cursor, uint16_t version, uint8_t has_sounding_delays) { int l; unsigned int nameLen = strlen(ins->inst_name) + 1; if (32 < nameLen) nameLen = 32; memcpy(cursor, ins->inst_name, nameLen); /*strncpy((char*)cursor, ins->inst_name, 32);*/ fromSint16BE(ins->note_offset, cursor + 32); cursor[34] = ins->percussion_key_number; cursor[35] = ins->fbalg; cursor[36] = ins->lfosens; for(l = 0; l < 4; l++) { size_t off = 37 + (size_t)(l) * 7; cursor[off + 0] = ins->operators[l].dtfm_30; cursor[off + 1] = ins->operators[l].level_40; cursor[off + 2] = ins->operators[l].rsatk_50; cursor[off + 3] = ins->operators[l].amdecay1_60; cursor[off + 4] = ins->operators[l].decay2_70; cursor[off + 5] = ins->operators[l].susrel_80; cursor[off + 6] = ins->operators[l].ssgeg_90; } if((version >= 2) && has_sounding_delays) { if((version < 3) && (ins->inst_flags & WOPN_Ins_IsBlank) != 0) { /* Null delays indicate the blank instrument in version 2 */ fromUint16BE(0, cursor + 65); fromUint16BE(0, cursor + 67); } else { fromUint16BE(ins->delay_on_ms, cursor + 65); fromUint16BE(ins->delay_off_ms, cursor + 67); } } } WOPNFile *WOPN_LoadBankFromMem(void *mem, size_t length, int *error) { WOPNFile *outFile = NULL; uint16_t i = 0, j = 0, k = 0; uint16_t version = 0; uint16_t count_melodic_banks = 1; uint16_t count_percussive_banks = 1; uint8_t *cursor = (uint8_t *)mem; WOPNBank *bankslots[2]; uint16_t bankslots_sizes[2]; #define SET_ERROR(err) \ {\ WOPN_Free(outFile);\ if(error)\ {\ *error = err;\ }\ } #define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; } if(!cursor) { SET_ERROR(WOPN_ERR_NULL_POINTER); return NULL; } {/* Magic number */ if(length < 11) { SET_ERROR(WOPN_ERR_UNEXPECTED_ENDING); return NULL; } if(memcmp(cursor, wopn2_magic1, 11) == 0) { version = 1; } else if(memcmp(cursor, wopn2_magic2, 11) != 0) { SET_ERROR(WOPN_ERR_BAD_MAGIC); return NULL; } GO_FORWARD(11); } if (version == 0) {/* Version code */ if(length < 2) { SET_ERROR(WOPN_ERR_UNEXPECTED_ENDING); return NULL; } version = toUint16LE(cursor); if(version > wopn_latest_version) { SET_ERROR(WOPN_ERR_NEWER_VERSION); return NULL; } GO_FORWARD(2); } {/* Header of WOPN */ uint8_t head[5]; if(length < 5) { SET_ERROR(WOPN_ERR_UNEXPECTED_ENDING); return NULL; } memcpy(head, cursor, 5); count_melodic_banks = toUint16BE(head); count_percussive_banks = toUint16BE(head + 2); GO_FORWARD(5); outFile = WOPN_Init(count_melodic_banks, count_percussive_banks); if(!outFile) { SET_ERROR(WOPN_ERR_OUT_OF_MEMORY); return NULL; } outFile->version = version; outFile->lfo_freq = head[4] & 0xf; if(version >= 2) outFile->chip_type = (head[4] >> 4) & 1; outFile->volume_model = 0; } bankslots_sizes[0] = count_melodic_banks; bankslots[0] = outFile->banks_melodic; bankslots_sizes[1] = count_percussive_banks; bankslots[1] = outFile->banks_percussive; if(version >= 2) /* Bank names and LSB/MSB titles */ { for(i = 0; i < 2; i++) { for(j = 0; j < bankslots_sizes[i]; j++) { if(length < 34) { SET_ERROR(WOPN_ERR_UNEXPECTED_ENDING); return NULL; } strncpy(bankslots[i][j].bank_name, (const char*)cursor, 32); bankslots[i][j].bank_name[32] = '\0'; bankslots[i][j].bank_midi_lsb = cursor[32]; bankslots[i][j].bank_midi_msb = cursor[33]; GO_FORWARD(34); } } } {/* Read instruments data */ uint16_t insSize = 0; if(version > 1) insSize = WOPN_INST_SIZE_V2; else insSize = WOPN_INST_SIZE_V1; for(i = 0; i < 2; i++) { if(length < (insSize * 128) * (size_t)bankslots_sizes[i]) { SET_ERROR(WOPN_ERR_UNEXPECTED_ENDING); return NULL; } for(j = 0; j < bankslots_sizes[i]; j++) { for(k = 0; k < 128; k++) { WOPNInstrument *ins = &bankslots[i][j].ins[k]; WOPN_parseInstrument(ins, cursor, version, 1); GO_FORWARD(insSize); } } } } #undef GO_FORWARD #undef SET_ERROR return outFile; } int WOPN_LoadInstFromMem(OPNIFile *file, void *mem, size_t length) { uint16_t version = 0; uint8_t *cursor = (uint8_t *)mem; uint16_t ins_size; if(!cursor) return WOPN_ERR_NULL_POINTER; #define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; } {/* Magic number */ if(length < 11) return WOPN_ERR_UNEXPECTED_ENDING; if(memcmp(cursor, opni_magic1, 11) == 0) version = 1; else if(memcmp(cursor, opni_magic2, 11) != 0) return WOPN_ERR_BAD_MAGIC; GO_FORWARD(11); } if (version == 0) {/* Version code */ if(length < 2) return WOPN_ERR_UNEXPECTED_ENDING; version = toUint16LE(cursor); if(version > wopn_latest_version) return WOPN_ERR_NEWER_VERSION; GO_FORWARD(2); } file->version = version; {/* is drum flag */ if(length < 1) return WOPN_ERR_UNEXPECTED_ENDING; file->is_drum = *cursor; GO_FORWARD(1); } if(version > 1) /* Skip sounding delays are not part of single-instrument file * two sizes of uint16_t will be subtracted */ ins_size = WOPN_INST_SIZE_V2 - (sizeof(uint16_t) * 2); else ins_size = WOPN_INST_SIZE_V1; if(length < ins_size) return WOPN_ERR_UNEXPECTED_ENDING; WOPN_parseInstrument(&file->inst, cursor, version, 0); GO_FORWARD(ins_size); return WOPN_ERR_OK; #undef GO_FORWARD } size_t WOPN_CalculateBankFileSize(WOPNFile *file, uint16_t version) { size_t final_size = 0; size_t ins_size = 0; if(version == 0) version = wopn_latest_version; if(!file) return 0; final_size += 11 + 2 + 2 + 2 + 1; /* * Magic number, * Version, * Count of melodic banks, * Count of percussive banks, * Chip specific flags */ if(version >= 2) { /* Melodic banks meta-data */ final_size += (32 + 1 + 1) * file->banks_count_melodic; /* Percussive banks meta-data */ final_size += (32 + 1 + 1) * file->banks_count_percussion; } if(version >= 2) ins_size = WOPN_INST_SIZE_V2; else ins_size = WOPN_INST_SIZE_V1; /* Melodic instruments */ final_size += (ins_size * 128) * file->banks_count_melodic; /* Percussive instruments */ final_size += (ins_size * 128) * file->banks_count_percussion; return final_size; } size_t WOPN_CalculateInstFileSize(OPNIFile *file, uint16_t version) { size_t final_size = 0; size_t ins_size = 0; if(version == 0) version = wopn_latest_version; if(!file) return 0; final_size += 11 + 1; /* * Magic number, * is percussive instrument */ /* Version */ if (version > 1) final_size += 2; if(version > 1) /* Skip sounding delays are not part of single-instrument file * two sizes of uint16_t will be subtracted */ ins_size = WOPN_INST_SIZE_V2 - (sizeof(uint16_t) * 2); else ins_size = WOPN_INST_SIZE_V1; final_size += ins_size; return final_size; } int WOPN_SaveBankToMem(WOPNFile *file, void *dest_mem, size_t length, uint16_t version, uint16_t force_gm) { uint8_t *cursor = (uint8_t *)dest_mem; uint16_t ins_size = 0; uint16_t i, j, k; uint16_t banks_melodic = force_gm ? 1 : file->banks_count_melodic; uint16_t banks_percussive = force_gm ? 1 : file->banks_count_percussion; WOPNBank *bankslots[2]; uint16_t bankslots_sizes[2]; if(version == 0) version = wopn_latest_version; #define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; } if(length < 11) return WOPN_ERR_UNEXPECTED_ENDING; if(version > 1) memcpy(cursor, wopn2_magic2, 11); else memcpy(cursor, wopn2_magic1, 11); GO_FORWARD(11); if(version > 1) { if(length < 2) return WOPN_ERR_UNEXPECTED_ENDING; fromUint16LE(version, cursor); GO_FORWARD(2); } if(length < 2) return WOPN_ERR_UNEXPECTED_ENDING; fromUint16BE(banks_melodic, cursor); GO_FORWARD(2); if(length < 2) return WOPN_ERR_UNEXPECTED_ENDING; fromUint16BE(banks_percussive, cursor); GO_FORWARD(2); if(length < 1) return WOPN_ERR_UNEXPECTED_ENDING; cursor[0] = file->lfo_freq & 0xf; if (version >= 2) cursor[0] |= (file->chip_type & 1) << 4; GO_FORWARD(1); bankslots[0] = file->banks_melodic; bankslots_sizes[0] = banks_melodic; bankslots[1] = file->banks_percussive; bankslots_sizes[1] = banks_percussive; if(version >= 2) { for(i = 0; i < 2; i++) { for(j = 0; j < bankslots_sizes[i]; j++) { if(length < 34) return WOPN_ERR_UNEXPECTED_ENDING; memcpy(cursor, bankslots[i][j].bank_name, 32); cursor[32] = bankslots[i][j].bank_midi_lsb; cursor[33] = bankslots[i][j].bank_midi_msb; GO_FORWARD(34); } } } {/* Write instruments data */ if(version >= 2) ins_size = WOPN_INST_SIZE_V2; else ins_size = WOPN_INST_SIZE_V1; for(i = 0; i < 2; i++) { if(length < (ins_size * 128) * (size_t)bankslots_sizes[i]) return WOPN_ERR_UNEXPECTED_ENDING; for(j = 0; j < bankslots_sizes[i]; j++) { for(k = 0; k < 128; k++) { WOPNInstrument *ins = &bankslots[i][j].ins[k]; WOPN_writeInstrument(ins, cursor, version, 1); GO_FORWARD(ins_size); } } } } return WOPN_ERR_OK; #undef GO_FORWARD } int WOPN_SaveInstToMem(OPNIFile *file, void *dest_mem, size_t length, uint16_t version) { uint8_t *cursor = (uint8_t *)dest_mem; uint16_t ins_size; if(!cursor) return WOPN_ERR_NULL_POINTER; if(version == 0) version = wopn_latest_version; #define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; } {/* Magic number */ if(length < 11) return WOPN_ERR_UNEXPECTED_ENDING; if(version > 1) memcpy(cursor, opni_magic2, 11); else memcpy(cursor, opni_magic1, 11); GO_FORWARD(11); } if (version > 1) {/* Version code */ if(length < 2) return WOPN_ERR_UNEXPECTED_ENDING; fromUint16LE(version, cursor); GO_FORWARD(2); } {/* is drum flag */ if(length < 1) return WOPN_ERR_UNEXPECTED_ENDING; *cursor = file->is_drum; GO_FORWARD(1); } if(version > 1) /* Skip sounding delays are not part of single-instrument file * two sizes of uint16_t will be subtracted */ ins_size = WOPN_INST_SIZE_V2 - (sizeof(uint16_t) * 2); else ins_size = WOPN_INST_SIZE_V1; if(length < ins_size) return WOPN_ERR_UNEXPECTED_ENDING; WOPN_writeInstrument(&file->inst, cursor, version, 0); GO_FORWARD(ins_size); return WOPN_ERR_OK; #undef GO_FORWARD } BambooTracker-0.6.5/BambooTracker/format/wopn_file.h000066400000000000000000000210451476276175200224160ustar00rootroot00000000000000/* * Wohlstand's OPN2 Bank File - a bank format to store OPN2 timbre data and setup * * Copyright (c) 2018 Vitaly Novichkov * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #ifndef WOPN_FILE_H #define WOPN_FILE_H #include #include #ifdef __cplusplus extern "C" { #endif #if !defined(__STDC_VERSION__) || (defined(__STDC_VERSION__) && (__STDC_VERSION__ < 199901L)) \ || defined(__STRICT_ANSI__) || !defined(__cplusplus) typedef signed char int8_t; typedef unsigned char uint8_t; typedef signed short int int16_t; typedef unsigned short int uint16_t; #endif /* Type of chip for which a bank has been designed */ typedef enum WOPN_ChipType { /* The Yamaha OPN2 chip, alias YM2612 YM3438 */ WOPN_Chip_OPN2 = 0, /* The Yamaha OPNA chip, alias YM2608 */ WOPN_Chip_OPNA = 1 } WOPN_ChipType; /* Volume scaling model implemented in the libOPNMIDI */ typedef enum WOPN_VolumeModel { WOPN_VM_Generic = 0 } WOPN_VolumeModel; typedef enum WOPN_InstrumentFlags { /* Is pseudo eight-operator (two 4-operator voices) instrument */ WOPN_Ins_Pseudo8op = 0x01, /* Is a blank instrument entry */ WOPN_Ins_IsBlank = 0x02, /* Mask of the flags range */ WOPN_Ins_ALL_MASK = 0x03 } WOPN_InstrumentFlags; /* Error codes */ typedef enum WOPN_ErrorCodes { WOPN_ERR_OK = 0, /* Magic number is not maching */ WOPN_ERR_BAD_MAGIC, /* Too short file */ WOPN_ERR_UNEXPECTED_ENDING, /* Zero banks count */ WOPN_ERR_INVALID_BANKS_COUNT, /* Version of file is newer than supported by current version of library */ WOPN_ERR_NEWER_VERSION, /* Out of memory */ WOPN_ERR_OUT_OF_MEMORY, /* Given null pointer memory data */ WOPN_ERR_NULL_POINTER } WOPN_ErrorCodes; /* OPN2 Oerators data */ typedef struct WOPNOperator { /* Detune and frequency multiplication register data */ uint8_t dtfm_30; /* Total level register data */ uint8_t level_40; /* Rate scale and attack register data */ uint8_t rsatk_50; /* Amplitude modulation enable and Decay-1 register data */ uint8_t amdecay1_60; /* Decay-2 register data */ uint8_t decay2_70; /* Sustain and Release register data */ uint8_t susrel_80; /* SSG-EG register data */ uint8_t ssgeg_90; } WOPNOperator; /* Instrument entry */ typedef struct WOPNInstrument { /* Title of the instrument */ char inst_name[34]; /* MIDI note key (half-tone) offset for an instrument (or a first voice in pseudo-4-op mode) */ int16_t note_offset; /* Reserved */ int8_t midi_velocity_offset; /* Percussion MIDI base tone number at which this drum will be played */ uint8_t percussion_key_number; /* Enum WOPN_InstrumentFlags */ uint8_t inst_flags; /* Feedback and Algorithm register data */ uint8_t fbalg; /* LFO Sensitivity register data */ uint8_t lfosens; /* Operators register data */ WOPNOperator operators[4]; /* Millisecond delay of sounding while key is on */ uint16_t delay_on_ms; /* Millisecond delay of sounding after key off */ uint16_t delay_off_ms; } WOPNInstrument; /* Bank entry */ typedef struct WOPNBank { /* Name of bank */ char bank_name[33]; /* MIDI Bank LSB code */ uint8_t bank_midi_lsb; /* MIDI Bank MSB code */ uint8_t bank_midi_msb; /* Instruments data of this bank */ WOPNInstrument ins[128]; } WOPNBank; /* Instrument data file */ typedef struct OPNIFile { /* Version of instrument file */ uint16_t version; /* Is this a percussion instrument */ uint8_t is_drum; /* Instrument data */ WOPNInstrument inst; } OPNIFile; /* Bank data file */ typedef struct WOPNFile { /* Version of bank file */ uint16_t version; /* Count of melodic banks in this file */ uint16_t banks_count_melodic; /* Count of percussion banks in this file */ uint16_t banks_count_percussion; /* Chip global LFO enable flag and frequency register data */ uint8_t lfo_freq; /* Chip type this bank is designed for */ uint8_t chip_type; /* Reserved (Enum WOPN_VolumeModel) */ uint8_t volume_model; /* dynamically allocated data Melodic banks array */ WOPNBank *banks_melodic; /* dynamically allocated data Percussive banks array */ WOPNBank *banks_percussive; } WOPNFile; /** * @brief Initialize blank WOPN data structure with allocated bank data * @param melodic_banks Count of melodic banks * @param percussive_banks Count of percussive banks * @return pointer to heap-allocated WOPN data structure or NULL when out of memory or incorrectly given banks counts */ extern WOPNFile *WOPN_Init(uint16_t melodic_banks, uint16_t percussive_banks); /** * @brief Clean up WOPN data file (all allocated bank arrays will be fried too) * @param file pointer to heap-allocated WOPN data structure */ extern void WOPN_Free(WOPNFile *file); /** * @brief Compare two bank entries * @param bank1 First bank * @param bank2 Second bank * @return 1 if banks are equal or 0 if there are different */ extern int WOPN_BanksCmp(const WOPNFile *bank1, const WOPNFile *bank2); /** * @brief Load WOPN bank file from the memory. * WOPN data structure will be allocated. (don't forget to clear it with WOPN_Free() after use!) * @param mem Pointer to memory block contains raw WOPN bank file data * @param length Length of given memory block * @param error pointer to integer to return an error code. Pass NULL if you don't want to use error codes. * @return Heap-allocated WOPN file data structure or NULL if any error has occouped */ extern WOPNFile *WOPN_LoadBankFromMem(void *mem, size_t length, int *error); /** * @brief Load WOPI instrument file from the memory. * You must allocate OPNIFile structure by yourself and give the pointer to it. * @param file Pointer to destination OPNIFile structure to fill it with parsed data. * @param mem Pointer to memory block contains raw WOPI instrument file data * @param length Length of given memory block * @return 0 if no errors occouped, or an error code of WOPN_ErrorCodes enumeration */ extern int WOPN_LoadInstFromMem(OPNIFile *file, void *mem, size_t length); /** * @brief Calculate the size of the output memory block * @param file Heap-allocated WOPN file data structure * @param version Destination version of the file * @return Size of the raw WOPN file data */ extern size_t WOPN_CalculateBankFileSize(WOPNFile *file, uint16_t version); /** * @brief Calculate the size of the output memory block * @param file Pointer to WOPI file data structure * @param version Destination version of the file * @return Size of the raw WOPI file data */ extern size_t WOPN_CalculateInstFileSize(OPNIFile *file, uint16_t version); /** * @brief Write raw WOPN into given memory block * @param file Heap-allocated WOPN file data structure * @param dest_mem Destination memory block pointer * @param length Length of destination memory block * @param version Wanted WOPN version * @param force_gm Force GM set in saved bank file * @return Error code or 0 on success */ extern int WOPN_SaveBankToMem(WOPNFile *file, void *dest_mem, size_t length, uint16_t version, uint16_t force_gm); /** * @brief Write raw WOPI into given memory block * @param file Pointer to WOPI file data structure * @param dest_mem Destination memory block pointer * @param length Length of destination memory block * @param version Wanted WOPI version * @return Error code or 0 on success */ extern int WOPN_SaveInstToMem(OPNIFile *file, void *dest_mem, size_t length, uint16_t version); #ifdef __cplusplus } #endif #endif /* WOPN_FILE_H */ BambooTracker-0.6.5/BambooTracker/gui/000077500000000000000000000000001476276175200175555ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/gui/bookmark_manager_form.cpp000066400000000000000000000177651476276175200246230ustar00rootroot00000000000000/* * Copyright (C) 2020-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "bookmark_manager_form.hpp" #include "ui_bookmark_manager_form.h" #include #include #include "song.hpp" #include "gui/gui_utils.hpp" BookmarkManagerForm::BookmarkManagerForm(std::weak_ptr core, bool showHex, QWidget *parent) : QWidget(parent), ui(new Ui::BookmarkManagerForm), bt_(core), curSong_(core.lock()->getCurrentSongNumber()) { ui->setupUi(this); setWindowFlags((windowFlags() & ~(Qt::WindowContextHelpButtonHint | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint)) | Qt::WindowStaysOnTopHint); setNumberSettings(showHex); initList(); ui->orderSpinBox->setDisplayIntegerBase(numBase_); ui->stepSpinBox->setDisplayIntegerBase(numBase_); insSc_ = std::make_unique(Qt::Key_Insert, ui->listWidget, nullptr, nullptr, Qt::WidgetShortcut); QObject::connect(insSc_.get(), &QShortcut::activated, this, &BookmarkManagerForm::on_createPushButton_clicked); delSc_ = std::make_unique(Qt::Key_Delete, ui->listWidget, nullptr, nullptr, Qt::WidgetShortcut); QObject::connect(delSc_.get(), &QShortcut::activated, this, &BookmarkManagerForm::on_removePushButton_clicked); mvUpSc_ = std::make_unique(Qt::CTRL | Qt::Key_Up, ui->listWidget, nullptr, nullptr, Qt::WidgetShortcut); QObject::connect(mvUpSc_.get(), &QShortcut::activated, this, &BookmarkManagerForm::on_upToolButton_clicked); mvDnSc_ = std::make_unique(Qt::CTRL | Qt::Key_Down, ui->listWidget, nullptr, nullptr, Qt::WidgetShortcut); QObject::connect(mvDnSc_.get(), &QShortcut::activated, this, &BookmarkManagerForm::on_downToolButton_clicked); } BookmarkManagerForm::~BookmarkManagerForm() { delete ui; } void BookmarkManagerForm::initList() { int size = static_cast(bt_.lock()->getBookmarkSize(curSong_)); for (int i = 0; i < size; ++i) { Bookmark bm = bt_.lock()->getBookmark(curSong_, i); addBookmark(gui_utils::utf8ToQString(bm.name), bm.order, bm.step, true); } } void BookmarkManagerForm::addBookmark(QString name, int order, int step, bool onlyUi) { ui->listWidget->addItem(createText(name, order, step)); if (!onlyUi) { bt_.lock()->addBookmark(curSong_, name.toUtf8().toStdString(), order, step); } } void BookmarkManagerForm::removeBookmark(int i) { delete ui->listWidget->takeItem(i); bt_.lock()->removeBookmark(curSong_, i); } QString BookmarkManagerForm::createText(QString name, int order, int step) { auto pos = QString("(%1,%2)").arg(order, numWidth_, numBase_, QChar('0')) .arg(step, numWidth_, numBase_, QChar('0')).toUpper(); return ((name.isEmpty() ? tr("Bookmark") : name) + " " + pos); } void BookmarkManagerForm::sortList(bool byPos) { int row = ui->listWidget->currentRow(); QString text = (row == -1) ? "" : ui->listWidget->currentItem()->text(); ui->listWidget->clear(); if (byPos) bt_.lock()->sortBookmarkByPosition(curSong_); else bt_.lock()->sortBookmarkByName(curSong_); initList(); for (int i = 0; i < ui->listWidget->count(); ++i) { QListWidgetItem* item = ui->listWidget->item(i); if (item->text() == text) { ui->listWidget->setCurrentRow(i); break; } } emit modified(); } void BookmarkManagerForm::onCurrentSongNumberChanged() { curSong_ = bt_.lock()->getCurrentSongNumber(); ui->listWidget->clear(); initList(); } void BookmarkManagerForm::onConfigurationChanged(bool showHex) { if ((showHex && numBase_ == 16) || (!showHex && numBase_ == 10)) return; setNumberSettings(showHex); for (int i = 0; i < ui->listWidget->count(); ++i) { Bookmark bm = bt_.lock()->getBookmark(curSong_, i); ui->listWidget->item(i)->setText(createText(gui_utils::utf8ToQString(bm.name), bm.order, bm.step)); } ui->orderSpinBox->setDisplayIntegerBase(numBase_); ui->stepSpinBox->setDisplayIntegerBase(numBase_); } void BookmarkManagerForm::onBookmarkToggleRequested(int order, int step) { std::vector idcs = bt_.lock()->findBookmarks(curSong_, order, step); if (idcs.empty()) { if (bt_.lock()->getBookmarkSize(curSong_) == 127) return; // Maximum size int i = static_cast(bt_.lock()->getBookmarkSize(curSong_)); addBookmark(tr("Bookmark %1").arg(i), order, step); } else { for (auto&& it = idcs.rbegin(); it != idcs.rend(); ++it) { removeBookmark(*it); // Remove from back to remain the position } } emit modified(); } void BookmarkManagerForm::onBookmarkJumpRequested(bool toNext, int order, int step) { if (!bt_.lock()->getBookmarkSize(curSong_)) return; Bookmark bm = toNext ? bt_.lock()->getNextBookmark(curSong_, order, step) : bt_.lock()->getPreviousBookmark(curSong_, order, step); ui->listWidget->setCurrentRow(bt_.lock()->findBookmarks(curSong_, bm.order, bm.step).front()); emit positionJumpRequested(bm.order, bm.step); } void BookmarkManagerForm::on_createPushButton_clicked() { if (bt_.lock()->getBookmarkSize(curSong_) == 127) return; // Maximum size addBookmark(ui->nameLineEdit->text(), ui->orderSpinBox->value(), ui->stepSpinBox->value()); emit modified(); } void BookmarkManagerForm::on_removePushButton_clicked() { int row = ui->listWidget->currentRow(); if (row == -1) return; removeBookmark(row); emit modified(); } void BookmarkManagerForm::on_clearPushButton_clicked() { if (!ui->listWidget->count()) return; ui->listWidget->clear(); bt_.lock()->clearBookmark(curSong_); emit modified(); } void BookmarkManagerForm::on_upToolButton_clicked() { int row = ui->listWidget->currentRow(); if (row < 1) return; bt_.lock()->swapBookmarks(curSong_, row, row - 1); ui->listWidget->clear(); initList(); ui->listWidget->setCurrentRow(row - 1); emit modified(); } void BookmarkManagerForm::on_downToolButton_clicked() { int row = ui->listWidget->currentRow(); if (row == -1 || ui->listWidget->count() - 2 < row) return; bt_.lock()->swapBookmarks(curSong_, row, row + 1); ui->listWidget->clear(); initList(); ui->listWidget->setCurrentRow(row + 1); emit modified(); } void BookmarkManagerForm::on_positionPushButton_clicked() { sortList(true); } void BookmarkManagerForm::on_namePushButton_clicked() { sortList(false); } void BookmarkManagerForm::on_listWidget_currentRowChanged(int currentRow) { if (currentRow == -1) return; Bookmark bm = bt_.lock()->getBookmark(curSong_, currentRow); ui->nameLineEdit->setText(gui_utils::utf8ToQString(bm.name)); ui->orderSpinBox->setValue(bm.order); ui->stepSpinBox->setValue(bm.step); } void BookmarkManagerForm::on_updatePushButton_clicked() { int row = ui->listWidget->currentRow(); if (row == -1) return; QString name = ui->nameLineEdit->text(); int order = ui->orderSpinBox->value(); int step = ui->stepSpinBox->value(); ui->listWidget->item(row)->setText(createText(name, order, step)); bt_.lock()->changeBookmark(curSong_, row, name.toUtf8().toStdString(), order, step); emit modified(); } void BookmarkManagerForm::on_listWidget_itemDoubleClicked(QListWidgetItem *item) { int row = ui->listWidget->row(item); Bookmark bm = bt_.lock()->getBookmark(curSong_, row); emit positionJumpRequested(bm.order, bm.step); } BambooTracker-0.6.5/BambooTracker/gui/bookmark_manager_form.hpp000066400000000000000000000054701476276175200246160ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef BOOKMARK_MANAGER_FORM_HPP #define BOOKMARK_MANAGER_FORM_HPP #include #include #include #include #include #include "bamboo_tracker.hpp" namespace Ui { class BookmarkManagerForm; } class BookmarkManagerForm : public QWidget { Q_OBJECT public: BookmarkManagerForm(std::weak_ptr core, bool showHex, QWidget *parent = nullptr); ~BookmarkManagerForm() override; signals: void positionJumpRequested(int order, int step); void modified(); public slots: void onCurrentSongNumberChanged(); void onConfigurationChanged(bool showHex); void onBookmarkToggleRequested(int order, int step); void onBookmarkJumpRequested(bool toNext, int order, int step); private slots: void on_createPushButton_clicked(); void on_removePushButton_clicked(); void on_clearPushButton_clicked(); void on_upToolButton_clicked(); void on_downToolButton_clicked(); void on_positionPushButton_clicked(); void on_namePushButton_clicked(); void on_listWidget_currentRowChanged(int currentRow); void on_updatePushButton_clicked(); void on_listWidget_itemDoubleClicked(QListWidgetItem *item); private: Ui::BookmarkManagerForm *ui; std::weak_ptr bt_; int curSong_; int numWidth_, numBase_; std::unique_ptr insSc_, delSc_, mvUpSc_, mvDnSc_; void initList(); inline void setNumberSettings(bool showHex) { if (showHex) { numWidth_ = 2; numBase_ = 16; } else { numWidth_ = 3; numBase_ = 10; } } void addBookmark(QString name, int order, int step, bool onlyUi = false); void removeBookmark(int i); QString createText(QString name, int order, int step); void sortList(bool byPos); }; #endif // BOOKMARK_MANAGER_FORM_HPP BambooTracker-0.6.5/BambooTracker/gui/bookmark_manager_form.ui000066400000000000000000000154761476276175200244530ustar00rootroot00000000000000 BookmarkManagerForm 0 0 300 320 Bookmark Manager Sort by 9 9 9 9 Position Name 9 9 0 0 ... Qt::UpArrow 0 0 ... Qt::DownArrow Remove Clear All 0 0 Qt::Vertical 20 40 0 0 Bookmark editor 255 Order Name 127 Step Qt::Horizontal 40 20 0 0 Create New Update listWidget upToolButton downToolButton removePushButton clearPushButton positionPushButton namePushButton nameLineEdit orderSpinBox stepSpinBox createPushButton updatePushButton BambooTracker-0.6.5/BambooTracker/gui/color_palette.cpp000066400000000000000000000435671476276175200231340ustar00rootroot00000000000000/* * Copyright (C) 2018-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "color_palette.hpp" #include #include "gui/gui_utils.hpp" ColorPalette::ColorPalette() { // Instrument list ilistTextColor = QColor::fromRgb(255, 255, 255, 255); ilistBackColor = QColor::fromRgb(0, 0, 0, 255); ilistSelBackColor = QColor::fromRgb(110, 90, 140, 255); ilistHovBackColor = QColor::fromRgb(255, 255, 255, 75); ilistHovSelBackColor = QColor::fromRgb(140, 120, 170, 255); // Instrument editor instFMEnvLine1Color = QColor::fromRgb(242, 38, 19, 255); instFMEnvLine2Color = QColor::fromRgb(46, 204, 113, 255); instFMEnvLine3Color = QColor::fromRgb(38, 38, 255, 255); instFMEnvGridColor = QColor::fromRgb(40, 40, 40, 63); instFMEnvBackColor = QColor::fromRgb(255, 255, 255, 0); instFMEnvBorderColor = QColor::fromRgb(125, 125, 125, 255); instFMAlForeColor = QColor::fromRgb(107, 185, 240, 255); instFMAlBackColor = QColor::fromRgb(255, 255, 255, 0); instSeqLoopBackColor = QColor::fromRgb(25, 25, 25, 255); instSeqReleaseBackColor = QColor::fromRgb(0, 0, 0, 255); instSeqLoopColor = QColor::fromRgb(210, 40, 180, 127); instSeqReleaseColor = QColor::fromRgb(40, 170, 200, 127); instSeqLoopEdgeColor = QColor::fromRgb(180, 20, 180, 127); instSeqReleaseEdgeColor = QColor::fromRgb(40, 170, 150, 127); instSeqTagColor = QColor::fromRgb(255, 255, 255, 255); instSeqHovColor = QColor::fromRgb(255, 255, 255, 63); instSeqLoopTextColor = QColor::fromRgb(24, 223, 172, 255); instSeqReleaseTextColor = QColor::fromRgb(24, 223, 172, 255); instSeqCellColor = QColor::fromRgb(38, 183, 173, 255); instSeqCellTextColor = QColor::fromRgb(255, 255, 255); instSeqBorderColor = QColor::fromRgb(50, 50, 50, 255); instSeqMaskColor = QColor::fromRgb(0, 0, 0, 128); instSeqOddColColor = QColor::fromRgb(255, 255, 255, 31); // ADPCM sample editor instADPCMMemAllColor = QColor::fromRgb(210, 40, 180, 150); instADPCMMemCurColor = QColor::fromRgb(210, 40, 180, 255); instADPCMMemBackColor = QColor::fromRgb(0, 0, 0, 255); instADPCMSampViewForeColor = QColor::fromRgb(38, 183, 173, 255); instADPCMSampViewBackColor = QColor::fromRgb(0, 0, 0, 255); instADPCMSampViewCenterColor = QColor::fromRgb(63, 63, 63, 255); instADPCMSampViewGridColor = QColor::fromRgb(63, 63, 63, 170); instADPCMSampViewDrawColor = QColor::fromRgb(255, 0, 0, 255); instADPCMSampViewDirectDrawColor = QColor::fromRgb(255, 150, 0, 255); instADPCMSampViewRepeatBeginColor = QColor::fromRgb(204, 102, 255, 255); instADPCMSampViewRepeatEndColor = QColor::fromRgb(137, 167, 255, 255); // Tone/Noise editor tnToneCellColor = QColor::fromRgb(225, 209, 47, 255); tnToneTextColor = QColor::fromRgb(255, 255, 126, 255); tnNoiseCellColor = QColor::fromRgb(210, 40, 180, 255); tnNoiseTextColor = QColor::fromRgb(240, 110, 220, 255); tnToneBackColor = QColor::fromRgb(0, 0, 0, 255); tnNoiseBackColor = QColor::fromRgb(25, 25, 25, 255); // Order list odrDefTextColor = QColor::fromRgb(180, 180, 180, 255); odrDefRowColor = QColor::fromRgb(40, 40, 80, 255); odrCurTextColor = QColor::fromRgb(255, 255, 255, 255); odrCurRowColor = QColor::fromRgb(110, 90, 140, 255); odrCurEditRowColor = QColor::fromRgb(140, 90, 110, 255); odrCurCellColor = QColor::fromRgb(255, 255, 255, 127); odrPlayRowColor = QColor::fromRgb(90, 90, 140, 255); odrSelCellColor = QColor::fromRgb(100, 100, 200, 192); odrHovCellColor = QColor::fromRgb(255, 255, 255, 64); odrRowNumColor = QColor::fromRgb(255, 200, 180, 255); odrHeaderTextColor = QColor::fromRgb(240, 240, 200, 255); odrHeaderRowColor = QColor::fromRgb(60, 60, 60, 255); odrBorderColor = QColor::fromRgb(0, 0, 0, 255); odrHeaderBorderColor = QColor::fromRgb(0, 0, 0, 255); odrBackColor = QColor::fromRgb(0, 0, 0, 255); odrUnfocusedShadowColor = QColor::fromRgb(0, 0, 0, 47); // Pattern editor ptnDefTextColor = QColor::fromRgb(180, 180, 180, 255); ptnDefStepColor = QColor::fromRgb(0, 0, 40, 255); ptnHl1StepColor = QColor::fromRgb(30, 40, 70, 255); ptnHl2StepColor = QColor::fromRgb(60, 60, 100, 255); ptnCurTextColor = QColor::fromRgb(255, 255, 255, 255); ptnCurStepColor = QColor::fromRgb(110, 90, 140, 255); ptnCurEditStepColor = QColor::fromRgb(140, 90, 110, 255); ptnCurCellColor = QColor::fromRgb(255, 255, 255, 127); ptnPlayStepColor = QColor::fromRgb(90, 90, 140, 255); ptnSelCellColor = QColor::fromRgb(100, 100, 200, 192); ptnHovCellColor = QColor::fromRgb(255, 255, 255, 64); ptnDefStepNumColor = QColor::fromRgb(255, 200, 180, 255); ptnHl1StepNumColor = QColor::fromRgb(255, 140, 160, 255); ptnHl2StepNumColor = QColor::fromRgb(255, 140, 160, 255); ptnNoteColor = QColor::fromRgb(210, 230, 64, 255); ptnInstColor = QColor::fromRgb(82, 179, 217, 255); ptnVolColor = QColor::fromRgb(226, 156, 80, 255); ptnEffColor = QColor::fromRgb(42, 187, 155, 255); ptnErrorColor = QColor::fromRgb(255, 0, 0, 255); ptnHeaderTextColor = QColor::fromRgb(240, 240, 200, 255); ptnHeaderRowColor = QColor::fromRgb(60, 60, 60, 255); ptnMaskColor = QColor::fromRgb(0, 0, 0, 127); ptnBorderColor = QColor::fromRgb(0, 0, 0, 255); ptnHeaderBorderColor = QColor::fromRgb(0, 0, 0, 255); ptnMuteColor = QColor::fromRgb(255, 0, 0, 255); ptnUnmuteColor = QColor::fromRgb(0, 255, 0, 255); ptnBackColor = QColor::fromRgb(0, 0, 0, 255); ptnMarkerColor = QColor::fromRgb(255, 255, 255, 128); ptnUnfocusedShadowColor = QColor::fromRgb(0, 0, 0, 47); // Wave visual wavBackColor = QColor::fromRgb(0, 0, 33, 255); wavDrawColor = QColor::fromRgb(82, 179, 217, 255); } namespace io { namespace { // config path (*nix): ~/.config//.ini const QString FILE = "BambooTracker"; void save(QSettings& settings, const ColorPalette* const palette) { settings.beginGroup("PatternEditor"); settings.setValue("defaultStepText", palette->ptnDefTextColor.name(QColor::HexArgb)); settings.setValue("defaultStepBackground", palette->ptnDefStepColor.name(QColor::HexArgb)); settings.setValue("highlightedStep1Background", palette->ptnHl1StepColor.name(QColor::HexArgb)); settings.setValue("highlightedStep2Background", palette->ptnHl2StepColor.name(QColor::HexArgb)); settings.setValue("currentStepText", palette->ptnCurTextColor.name(QColor::HexArgb)); settings.setValue("currentStepBackground", palette->ptnCurStepColor.name(QColor::HexArgb)); settings.setValue("currentEditingStepBackground", palette->ptnCurEditStepColor.name(QColor::HexArgb)); settings.setValue("currentCellBackground", palette->ptnCurCellColor.name(QColor::HexArgb)); settings.setValue("currentPlayingStepBackground", palette->ptnPlayStepColor.name(QColor::HexArgb)); settings.setValue("selectionBackground", palette->ptnSelCellColor.name(QColor::HexArgb)); settings.setValue("hoveredCellBackground", palette->ptnHovCellColor.name(QColor::HexArgb)); settings.setValue("defaultStepNumber", palette->ptnDefStepNumColor.name(QColor::HexArgb)); settings.setValue("highlightedStep1Number", palette->ptnHl1StepNumColor.name(QColor::HexArgb)); settings.setValue("highlightedStep2Number", palette->ptnHl2StepNumColor.name(QColor::HexArgb)); settings.setValue("noteText", palette->ptnNoteColor.name(QColor::HexArgb)); settings.setValue("instrumentText", palette->ptnInstColor.name(QColor::HexArgb)); settings.setValue("volumeText", palette->ptnVolColor.name(QColor::HexArgb)); settings.setValue("effectText", palette->ptnEffColor.name(QColor::HexArgb)); settings.setValue("errorText", palette->ptnErrorColor.name(QColor::HexArgb)); settings.setValue("headerText", palette->ptnHeaderTextColor.name(QColor::HexArgb)); settings.setValue("headerBackground", palette->ptnHeaderRowColor.name(QColor::HexArgb)); settings.setValue("mask", palette->ptnMaskColor.name(QColor::HexArgb)); settings.setValue("border", palette->ptnBorderColor.name(QColor::HexArgb)); settings.setValue("headerBorder", palette->ptnHeaderBorderColor.name(QColor::HexArgb)); settings.setValue("mute", palette->ptnMuteColor.name(QColor::HexArgb)); settings.setValue("unmute", palette->ptnUnmuteColor.name(QColor::HexArgb)); settings.setValue("background", palette->ptnBackColor.name(QColor::HexArgb)); settings.setValue("marker", palette->ptnMarkerColor.name(QColor::HexArgb)); settings.setValue("unfocusedShadow", palette->ptnUnfocusedShadowColor.name(QColor::HexArgb)); settings.endGroup(); settings.beginGroup("OrderList"); settings.setValue("defaultRowText", palette->odrDefTextColor.name(QColor::HexArgb)); settings.setValue("defaultRowBackground", palette->odrDefRowColor.name(QColor::HexArgb)); settings.setValue("currentRowText", palette->odrCurTextColor.name(QColor::HexArgb)); settings.setValue("currentRowBackground", palette->odrCurRowColor.name(QColor::HexArgb)); settings.setValue("currentEditingRowBackground", palette->odrCurEditRowColor.name(QColor::HexArgb)); settings.setValue("currentCellBackground", palette->odrCurCellColor.name(QColor::HexArgb)); settings.setValue("currentPlayingRowBackground", palette->odrPlayRowColor.name(QColor::HexArgb)); settings.setValue("selectionBackground", palette->odrSelCellColor.name(QColor::HexArgb)); settings.setValue("hoveredCellBackground", palette->odrHovCellColor.name(QColor::HexArgb)); settings.setValue("rowNumber", palette->odrRowNumColor.name(QColor::HexArgb)); settings.setValue("headerText", palette->odrHeaderTextColor.name(QColor::HexArgb)); settings.setValue("headerBackground", palette->odrHeaderRowColor.name(QColor::HexArgb)); settings.setValue("border", palette->odrBorderColor.name(QColor::HexArgb)); settings.setValue("headerBorder", palette->odrHeaderBorderColor.name(QColor::HexArgb)); settings.setValue("background", palette->odrBackColor.name(QColor::HexArgb)); settings.setValue("unfocusedShadow", palette->odrUnfocusedShadowColor.name(QColor::HexArgb)); settings.endGroup(); settings.beginGroup("InstrumentList"); settings.setValue("defaultText", palette->ilistTextColor.name(QColor::HexArgb)); settings.setValue("background", palette->ilistBackColor.name(QColor::HexArgb)); settings.setValue("selectedBackground", palette->ilistSelBackColor.name(QColor::HexArgb)); settings.setValue("hoveredBackground", palette->ilistHovBackColor.name(QColor::HexArgb)); settings.setValue("selectedHoveredBackground", palette->ilistHovSelBackColor.name(QColor::HexArgb)); settings.endGroup(); settings.beginGroup("Oscilloscope"); settings.setValue("background", palette->wavBackColor.name(QColor::HexArgb)); settings.setValue("foreground", palette->wavDrawColor.name(QColor::HexArgb)); settings.endGroup(); } void load(QSettings& settings, ColorPalette* const palette) { settings.beginGroup("PatternEditor"); palette->ptnDefTextColor = settings.value("defaultStepText", palette->ptnDefTextColor).value(); palette->ptnDefStepColor = settings.value("defaultStepBackground", palette->ptnDefStepColor).value(); palette->ptnHl1StepColor = settings.value("highlightedStep1Background", palette->ptnHl1StepColor).value(); palette->ptnHl2StepColor = settings.value("highlightedStep2Background", palette->ptnHl2StepColor).value(); palette->ptnCurTextColor = settings.value("currentStepText", palette->ptnCurTextColor).value(); palette->ptnCurStepColor = settings.value("currentStepBackground", palette->ptnCurStepColor).value(); palette->ptnCurEditStepColor = settings.value("currentEditingStepBackground", palette->ptnCurEditStepColor).value(); palette->ptnCurCellColor = settings.value("currentCellBackground", palette->ptnCurCellColor).value(); palette->ptnPlayStepColor = settings.value("currentPlayingStepBackground", palette->ptnPlayStepColor).value(); palette->ptnSelCellColor = settings.value("selectionBackground", palette->ptnSelCellColor).value(); palette->ptnHovCellColor = settings.value("hoveredCellBackground", palette->ptnHovCellColor).value(); palette->ptnDefStepNumColor = settings.value("defaultStepNumber", palette->ptnDefStepNumColor).value(); palette->ptnHl1StepNumColor = settings.value("highlightedStep1Number", palette->ptnHl1StepNumColor).value(); palette->ptnHl2StepNumColor = settings.value("highlightedStep2Number", palette->ptnHl2StepNumColor).value(); palette->ptnNoteColor = settings.value("noteText", palette->ptnNoteColor).value(); palette->ptnInstColor = settings.value("instrumentText", palette->ptnInstColor).value(); palette->ptnVolColor = settings.value("volumeText", palette->ptnVolColor).value(); palette->ptnEffColor = settings.value("effectText", palette->ptnEffColor).value(); palette->ptnErrorColor = settings.value("errorText", palette->ptnErrorColor).value(); palette->ptnHeaderTextColor = settings.value("headerText", palette->ptnHeaderTextColor).value(); palette->ptnHeaderRowColor = settings.value("headerBackground", palette->ptnHeaderRowColor).value(); palette->ptnMaskColor = settings.value("mask", palette->ptnMaskColor).value(); palette->ptnBorderColor = settings.value("border", palette->ptnBorderColor).value(); palette->ptnHeaderBorderColor = settings.value("headerBorder", palette->ptnHeaderBorderColor).value(); palette->ptnMuteColor = settings.value("mute", palette->ptnMuteColor).value(); palette->ptnUnmuteColor = settings.value("unmute", palette->ptnUnmuteColor).value(); palette->ptnBackColor = settings.value("background", palette->ptnBackColor).value(); palette->ptnMarkerColor = settings.value("marker", palette->ptnMarkerColor).value(); palette->ptnUnfocusedShadowColor = settings.value("unfocusedShadow", palette->ptnUnfocusedShadowColor).value(); settings.endGroup(); settings.beginGroup("OrderList"); palette->odrDefTextColor = settings.value("defaultRowText", palette->odrDefTextColor).value(); palette->odrDefRowColor = settings.value("defaultRowBackground", palette->odrDefRowColor).value(); palette->odrCurTextColor = settings.value("currentRowText", palette->odrCurTextColor).value(); palette->odrCurRowColor = settings.value("currentRowBackground", palette->odrCurRowColor).value(); palette->odrCurEditRowColor = settings.value("currentEditingRowBackground", palette->odrCurEditRowColor).value(); palette->odrCurCellColor = settings.value("currentCellBackground", palette->odrCurCellColor).value(); palette->odrPlayRowColor = settings.value("currentPlayingRowBackground", palette->odrPlayRowColor).value(); palette->odrSelCellColor = settings.value("selectionBackground", palette->odrSelCellColor).value(); palette->odrHovCellColor = settings.value("hoveredCellBackground", palette->odrHovCellColor).value(); palette->odrRowNumColor = settings.value("rowNumber", palette->odrRowNumColor).value(); palette->odrHeaderTextColor = settings.value("headerText", palette->odrHeaderTextColor).value(); palette->odrHeaderRowColor = settings.value("headerBackground", palette->odrHeaderRowColor).value(); palette->odrBorderColor = settings.value("border", palette->odrBorderColor).value(); palette->odrHeaderBorderColor = settings.value("headerBorder", palette->odrHeaderBorderColor).value(); palette->odrBackColor = settings.value("background", palette->odrBackColor).value(); palette->odrUnfocusedShadowColor = settings.value("unfocusedShadow", palette->odrUnfocusedShadowColor).value(); settings.endGroup(); settings.beginGroup("InstrumentList"); palette->ilistTextColor = settings.value("defaultText", palette->ilistTextColor).value(); palette->ilistBackColor = settings.value("background", palette->ilistBackColor).value(); palette->ilistSelBackColor = settings.value("selectedBackground", palette->ilistSelBackColor).value(); palette->ilistHovBackColor = settings.value("hoveredBackground", palette->ilistHovBackColor).value(); palette->ilistHovSelBackColor = settings.value("selectedHoveredBackground", palette->ilistHovSelBackColor).value(); settings.endGroup(); settings.beginGroup("Oscilloscope"); palette->wavBackColor = settings.value("background", palette->wavBackColor).value(); palette->wavDrawColor = settings.value("foreground", palette->wavDrawColor).value(); settings.endGroup(); } } bool savePalette(const ColorPalette* const palette) { try { QSettings settings(QSettings::IniFormat, QSettings::UserScope, io::ORGANIZATION_NAME, FILE); save(settings, palette); return true; } catch (...) { return false; } } bool savePalette(const QString &file, const ColorPalette* const palette) { try { QSettings settings(file, QSettings::IniFormat); save(settings, palette); return true; } catch (...) { return false; } } bool loadPalette(ColorPalette* const palette) { try { QSettings settings(QSettings::IniFormat, QSettings::UserScope, io::ORGANIZATION_NAME, FILE); load(settings, palette); return true; } catch (...) { return false; } } bool loadPalette(const QString &file, ColorPalette* const palette) { try { QSettings settings(file, QSettings::IniFormat); load(settings, palette); return true; } catch (...) { return false; } } } BambooTracker-0.6.5/BambooTracker/gui/color_palette.hpp000066400000000000000000000074731476276175200231350ustar00rootroot00000000000000/* * Copyright (C) 2018-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef COLOR_PALETTE_HPP #define COLOR_PALETTE_HPP #include class ColorPalette { public: ColorPalette(); // Instrument list QColor ilistTextColor, ilistBackColor; QColor ilistSelBackColor; QColor ilistHovBackColor; QColor ilistHovSelBackColor; // Instrument editor QColor instFMEnvLine1Color, instFMEnvLine2Color, instFMEnvLine3Color; QColor instFMEnvGridColor; QColor instFMEnvBackColor, instFMEnvBorderColor; QColor instFMAlForeColor, instFMAlBackColor; QColor instSeqTagColor; QColor instSeqHovColor; QColor instSeqLoopBackColor, instSeqLoopColor, instSeqLoopEdgeColor; QColor instSeqReleaseBackColor, instSeqReleaseColor, instSeqReleaseEdgeColor; QColor instSeqLoopTextColor, instSeqReleaseTextColor; QColor instSeqCellColor, instSeqCellTextColor; QColor instSeqBorderColor; QColor instSeqMaskColor; QColor instSeqOddColColor; // ADPCM sample editor QColor instADPCMMemAllColor, instADPCMMemCurColor, instADPCMMemBackColor; QColor instADPCMSampViewForeColor, instADPCMSampViewBackColor, instADPCMSampViewCenterColor; QColor instADPCMSampViewGridColor, instADPCMSampViewDrawColor, instADPCMSampViewDirectDrawColor; QColor instADPCMSampViewRepeatBeginColor, instADPCMSampViewRepeatEndColor; // Tone/Noise editor QColor tnToneCellColor, tnToneTextColor; QColor tnNoiseCellColor, tnNoiseTextColor; QColor tnToneBackColor, tnNoiseBackColor; // Order list QColor odrDefTextColor, odrDefRowColor; QColor odrCurTextColor, odrCurRowColor; QColor odrCurEditRowColor; QColor odrCurCellColor; QColor odrPlayRowColor; QColor odrSelCellColor; QColor odrHovCellColor; QColor odrRowNumColor; QColor odrHeaderTextColor, odrHeaderRowColor; QColor odrBorderColor, odrHeaderBorderColor; QColor odrBackColor; QColor odrUnfocusedShadowColor; // Pattern editor QColor ptnDefTextColor, ptnDefStepColor, ptnHl1StepColor, ptnHl2StepColor; QColor ptnCurTextColor, ptnCurStepColor, ptnCurEditStepColor, ptnCurCellColor; QColor ptnPlayStepColor; QColor ptnSelCellColor; QColor ptnHovCellColor; QColor ptnDefStepNumColor, ptnHl1StepNumColor, ptnHl2StepNumColor; QColor ptnNoteColor, ptnInstColor, ptnVolColor, ptnEffColor; QColor ptnErrorColor; QColor ptnHeaderTextColor, ptnHeaderRowColor; QColor ptnMaskColor; QColor ptnBorderColor, ptnHeaderBorderColor; QColor ptnMuteColor, ptnUnmuteColor; QColor ptnBackColor; QColor ptnMarkerColor; QColor ptnUnfocusedShadowColor; // Wave visual QColor wavBackColor; QColor wavDrawColor; }; namespace io { bool savePalette(const ColorPalette* const palette); bool savePalette(const QString& file, const ColorPalette* const palette); bool loadPalette(ColorPalette* const palette); bool loadPalette(const QString& file, ColorPalette* const palette); } #endif // COLOR_PALETTE_HPP BambooTracker-0.6.5/BambooTracker/gui/command/000077500000000000000000000000001476276175200211735ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/gui/command/instrument/000077500000000000000000000000001476276175200234035ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/gui/command/instrument/add_instrument_qt_command.cpp000066400000000000000000000044531476276175200313370ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "add_instrument_qt_command.hpp" #include #include "command/command_id.hpp" #include "instrument_command_qt_utils.hpp" AddInstrumentQtCommand::AddInstrumentQtCommand(QListWidget *list, int num, const QString& name, InstrumentType type, std::weak_ptr dialogMan, MainWindow* mainWin, bool onlyUsed, bool preventFirstStore, QUndoCommand *parent) : QUndoCommand(parent), list_(list), num_(num), name_(name), type_(type), dialogMan_(dialogMan), mainWin_(mainWin), onlyUsed_(onlyUsed), hasDone_(!preventFirstStore) { } void AddInstrumentQtCommand::undo() { auto&& item = list_->takeItem(num_); delete item; dialogMan_.lock()->remove(num_); if ((type_ == InstrumentType::ADPCM || type_ == InstrumentType::Drumkit) && onlyUsed_) { mainWin_->assignADPCMSamples(); } } void AddInstrumentQtCommand::redo() { list_->insertItem(num_, gui_command_utils::createInstrumentListItem(num_, type_, name_)); if (hasDone_ && (type_ == InstrumentType::ADPCM || type_ == InstrumentType::Drumkit)) { mainWin_->assignADPCMSamples(); } hasDone_ = true; } int AddInstrumentQtCommand::id() const { return CommandId::AddInstrument; } BambooTracker-0.6.5/BambooTracker/gui/command/instrument/add_instrument_qt_command.hpp000066400000000000000000000040601476276175200313360ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef ADD_INSTRUMENT_QT_COMMAND_HPP #define ADD_INSTRUMENT_QT_COMMAND_HPP #include #include #include #include #include #include "gui/mainwindow.hpp" #include "gui/instrument_editor/instrument_editor_manager.hpp" enum class InstrumentType; class AddInstrumentQtCommand final : public QUndoCommand { public: AddInstrumentQtCommand(QListWidget *list, int num, const QString& name, InstrumentType type, std::weak_ptr dialogMan, MainWindow* mainWin, bool onlyUsed, bool preventFirstStore = false, QUndoCommand *parent = nullptr); void undo() override; void redo() override; int id() const override; private: QListWidget *list_; const int num_; const QString name_; const InstrumentType type_; std::weak_ptr dialogMan_; MainWindow* mainWin_; const bool onlyUsed_; bool hasDone_; }; #endif // ADD_INSTRUMENT_QT_COMMAND_HPP BambooTracker-0.6.5/BambooTracker/gui/command/instrument/change_instrument_name_qt_command.cpp000066400000000000000000000042521476276175200330310ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "change_instrument_name_qt_command.hpp" #include "command/command_id.hpp" #include "gui/instrument_editor/instrument_editor.hpp" #include "instrument_command_qt_utils.hpp" ChangeInstrumentNameQtCommand::ChangeInstrumentNameQtCommand(QListWidget* list, int num, int row, std::weak_ptr editorMan, const QString& oldName, const QString& newName, QUndoCommand* parent) : QUndoCommand(parent), list_(list), num_(num), row_(row), editorMan_(editorMan), oldName_(oldName), newName_(newName) { } void ChangeInstrumentNameQtCommand::redo() { auto item = list_->item(row_); auto title = gui_command_utils::makeInstrumentListText(num_, newName_); item->setText(title); editorMan_.lock()->onInstrumentNameChanged(num_); } void ChangeInstrumentNameQtCommand::undo() { auto item = list_->item(row_); auto title = gui_command_utils::makeInstrumentListText(num_, oldName_); item->setText(title); editorMan_.lock()->onInstrumentNameChanged(num_); } int ChangeInstrumentNameQtCommand::id() const { return CommandId::ChangeInstrumentName; } BambooTracker-0.6.5/BambooTracker/gui/command/instrument/change_instrument_name_qt_command.hpp000066400000000000000000000036321476276175200330370ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef CHANGE_INSTRUMENT_NAME_QT_COMMAND_HPP #define CHANGE_INSTRUMENT_NAME_QT_COMMAND_HPP #include #include #include #include #include "gui/instrument_editor/instrument_editor_manager.hpp" class ChangeInstrumentNameQtCommand final : public QUndoCommand { public: ChangeInstrumentNameQtCommand(QListWidget* list, int num, int row, std::weak_ptr editorMan, const QString& oldName, const QString& newName, QUndoCommand* parent = nullptr); void undo() override; void redo() override; int id() const override; private: QListWidget *list_; const int num_; const int row_; std::weak_ptr editorMan_; const QString oldName_, newName_; }; #endif // CHANGE_INSTRUMENT_NAME_QT_COMMAND_HPP BambooTracker-0.6.5/BambooTracker/gui/command/instrument/clone_instrument_qt_command.cpp000066400000000000000000000036601476276175200317060ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "clone_instrument_qt_command.hpp" #include "instrument.hpp" #include "command/command_id.hpp" #include "instrument_command_qt_utils.hpp" CloneInstrumentQtCommand::CloneInstrumentQtCommand(QListWidget *list, int num, InstrumentType type, const QString& name, std::weak_ptr dialogMan, QUndoCommand *parent) : QUndoCommand(parent), list_(list), cloneNum_(num), dialogMan_(dialogMan), type_(type), name_(name) { } void CloneInstrumentQtCommand::redo() { list_->insertItem(cloneNum_, gui_command_utils::createInstrumentListItem(cloneNum_, type_, name_)); } void CloneInstrumentQtCommand::undo() { auto&& item = list_->takeItem(cloneNum_); delete item; dialogMan_.lock()->remove(cloneNum_); } int CloneInstrumentQtCommand::id() const { return CommandId::CloneInstrument; } BambooTracker-0.6.5/BambooTracker/gui/command/instrument/clone_instrument_qt_command.hpp000066400000000000000000000035501476276175200317110ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef CLONE_INSTRUMENT_QT_COMMAND_H #define CLONE_INSTRUMENT_QT_COMMAND_H #include #include #include #include "gui/instrument_editor/instrument_editor_manager.hpp" enum class InstrumentType; class CloneInstrumentQtCommand final : public QUndoCommand { public: CloneInstrumentQtCommand(QListWidget *list, int num, InstrumentType type, const QString& name, std::weak_ptr dialogMan, QUndoCommand* parent = nullptr); void undo() override; void redo() override; int id() const override; private: QListWidget* list_; const int cloneNum_; std::weak_ptr dialogMan_; const InstrumentType type_; const QString name_; }; #endif // CLONE_INSTRUMENT_QT_COMMAND_H BambooTracker-0.6.5/BambooTracker/gui/command/instrument/deep_clone_instrument_qt_command.cpp000066400000000000000000000044361476276175200327050ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "deep_clone_instrument_qt_command.hpp" #include "instrument.hpp" #include "command/command_id.hpp" #include "instrument_command_qt_utils.hpp" DeepCloneInstrumentQtCommand::DeepCloneInstrumentQtCommand(QListWidget *list, int num, InstrumentType type, const QString& name, std::weak_ptr dialogMan, MainWindow* mainWin, bool onlyUsed, QUndoCommand *parent) : QUndoCommand(parent), list_(list), cloneNum_(num), dialogMan_(dialogMan), type_(type), name_(name), mainWin_(mainWin), onlyUsed_(onlyUsed) { } void DeepCloneInstrumentQtCommand::redo() { list_->insertItem(cloneNum_, gui_command_utils::createInstrumentListItem(cloneNum_, type_, name_)); if (type_ == InstrumentType::ADPCM || type_ == InstrumentType::Drumkit) mainWin_->assignADPCMSamples(); } void DeepCloneInstrumentQtCommand::undo() { auto&& item = list_->takeItem(cloneNum_); delete item; dialogMan_.lock()->remove(cloneNum_); if ((type_ == InstrumentType::ADPCM || type_ == InstrumentType::Drumkit) && onlyUsed_) { mainWin_->assignADPCMSamples(); } } int DeepCloneInstrumentQtCommand::id() const { return CommandId::DeepCloneInstrument; } BambooTracker-0.6.5/BambooTracker/gui/command/instrument/deep_clone_instrument_qt_command.hpp000066400000000000000000000037701476276175200327120ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef DEEP_CLONE_INSTRUMENT_QT_COMMAND_HPP #define DEEP_CLONE_INSTRUMENT_QT_COMMAND_HPP #include #include #include #include "gui/mainwindow.hpp" #include "gui/instrument_editor/instrument_editor_manager.hpp" enum class InstrumentType; class DeepCloneInstrumentQtCommand final : public QUndoCommand { public: DeepCloneInstrumentQtCommand(QListWidget *list, int num, InstrumentType type, const QString& name, std::weak_ptr dialogMan, MainWindow* mainWin, bool onlyUsed, QUndoCommand* parent = nullptr); void undo() override; void redo() override; int id() const override; private: QListWidget* list_; const int cloneNum_; std::weak_ptr dialogMan_; const InstrumentType type_; const QString name_; MainWindow* mainWin_; const bool onlyUsed_; }; #endif // DEEP_CLONE_INSTRUMENT_QT_COMMAND_HPP BambooTracker-0.6.5/BambooTracker/gui/command/instrument/instrument_command_qt_utils.cpp000066400000000000000000000034711476276175200317460ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "instrument_command_qt_utils.hpp" #include #include #include "instrument.hpp" #include "enum_hash.hpp" namespace gui_command_utils { namespace { const std::unordered_map ICON_SRC = { { InstrumentType::FM, ":/icon/inst_fm" }, { InstrumentType::SSG, ":/icon/inst_ssg" }, { InstrumentType::ADPCM, ":/icon/inst_adpcm" }, { InstrumentType::Drumkit, ":/icon/inst_kit" } }; } QListWidgetItem* createInstrumentListItem(int num, InstrumentType type, const QString& name) { QListWidgetItem *item = new QListWidgetItem(QIcon(ICON_SRC.at(type)), makeInstrumentListText(num, name)); item->setSizeHint(QSize(130, 17)); item->setData(Qt::UserRole, num); return item; } } BambooTracker-0.6.5/BambooTracker/gui/command/instrument/instrument_command_qt_utils.hpp000066400000000000000000000030721476276175200317500ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef INSTRUMENT_COMMAND_QT_UTILS_HPP #define INSTRUMENT_COMMAND_QT_UTILS_HPP #include #include enum class InstrumentType; namespace gui_command_utils { inline QString makeInstrumentListText(int num, const QString& name) { return QString("%1: %2").arg(num, 2, 16, QChar('0')).toUpper().arg(name); } QListWidgetItem* createInstrumentListItem(int num, InstrumentType type, const QString& name); } #endif // INSTRUMENT_COMMAND_QT_UTILS_HPP BambooTracker-0.6.5/BambooTracker/gui/command/instrument/instrument_commands_qt.hpp000066400000000000000000000030071476276175200307110ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef INSTRUMENT_COMMANDS_QT_HPP #define INSTRUMENT_COMMANDS_QT_HPP /********** Instrument edit **********/ #include "add_instrument_qt_command.hpp" #include "remove_instrument_qt_command.hpp" #include "change_instrument_name_qt_command.hpp" #include "clone_instrument_qt_command.hpp" #include "deep_clone_instrument_qt_command.hpp" #include "swap_instruments_qt_command.hpp" #endif // INSTRUMENT_COMMANDS_QT_HPP BambooTracker-0.6.5/BambooTracker/gui/command/instrument/remove_instrument_qt_command.cpp000066400000000000000000000045121476276175200321000ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "remove_instrument_qt_command.hpp" #include #include "instrument.hpp" #include "command/command_id.hpp" #include "instrument_command_qt_utils.hpp" RemoveInstrumentQtCommand::RemoveInstrumentQtCommand(QListWidget *list, int num, int row, const QString& name, InstrumentType type, std::weak_ptr dialogMan, MainWindow* mainWin, bool updateRequested, QUndoCommand *parent) : QUndoCommand(parent), list_(list), num_(num), name_(name), row_(row), type_(type), dialogMan_(dialogMan), mainWin_(mainWin), updateRequested_(updateRequested) { } void RemoveInstrumentQtCommand::undo() { list_->insertItem(row_, gui_command_utils::createInstrumentListItem(num_, type_, name_)); if (updateRequested_ && (type_ == InstrumentType::ADPCM || type_ == InstrumentType::Drumkit)) { mainWin_->assignADPCMSamples(); } } void RemoveInstrumentQtCommand::redo() { auto&& item = list_->takeItem(row_); delete item; dialogMan_.lock()->remove(num_); if (updateRequested_ && (type_ == InstrumentType::ADPCM || type_ == InstrumentType::Drumkit)) { mainWin_->assignADPCMSamples(); } } int RemoveInstrumentQtCommand::id() const { return CommandId::RemoveInstrument; } BambooTracker-0.6.5/BambooTracker/gui/command/instrument/remove_instrument_qt_command.hpp000066400000000000000000000040561476276175200321100ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef REMOVE_INSTRUMENT_QT_COMMAND_HPP #define REMOVE_INSTRUMENT_QT_COMMAND_HPP #include #include #include #include #include #include "gui/mainwindow.hpp" #include "gui/instrument_editor/instrument_editor_manager.hpp" enum class InstrumentType; class RemoveInstrumentQtCommand final : public QUndoCommand { public: RemoveInstrumentQtCommand(QListWidget *list, int num, int row, const QString& name, InstrumentType type, std::weak_ptr dialogMan, MainWindow* mainWin, bool updateRequested, QUndoCommand *parent = nullptr); void undo() override; void redo() override; int id() const override; private: QListWidget *list_; const int num_; const QString name_; const int row_; const InstrumentType type_; std::weak_ptr dialogMan_; MainWindow* mainWin_; const bool updateRequested_; }; #endif // REMOVE_INSTRUMENT_QT_COMMAND_HPP BambooTracker-0.6.5/BambooTracker/gui/command/instrument/swap_instruments_qt_command.cpp000066400000000000000000000060261476276175200317420ustar00rootroot00000000000000/* * Copyright (C) 2020-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "swap_instruments_qt_command.hpp" #include #include "instrument.hpp" #include "command/command_id.hpp" #include "gui/pattern_editor/pattern_editor.hpp" #include "gui/instrument_editor/instrument_editor.hpp" #include "instrument_command_qt_utils.hpp" SwapInstrumentsQtCommand::SwapInstrumentsQtCommand(QListWidget* list, int inst1Row, int inst2Row, const QString& inst1Name, const QString& inst2Name, std::weak_ptr editorMan, PatternEditor* pattern, QUndoCommand* parent) : QUndoCommand(parent), list_(list), ptn_(pattern), editorMan_(editorMan) { if (inst1Row < inst2Row) { inst1Row_ = inst1Row; inst2Row_ = inst2Row; inst1Name_ = inst1Name; inst2Name_ = inst2Name; } else { inst1Row_ = inst2Row; inst2Row_ = inst1Row; inst1Name_ = inst2Name; inst2Name_ = inst1Name; } } void SwapInstrumentsQtCommand::undo() { swap(inst1Row_, inst2Row_, inst2Name_, inst1Name_); } void SwapInstrumentsQtCommand::redo() { swap(inst1Row_, inst2Row_, inst1Name_, inst2Name_); } int SwapInstrumentsQtCommand::id() const { return CommandId::SwapInstruments; } void SwapInstrumentsQtCommand::swap(int above, int below, QString aboveName, QString belowName) { QListWidgetItem* belowItem = list_->takeItem(below); int belowId = belowItem->data(Qt::UserRole).toInt(); QListWidgetItem* aboveItem = list_->takeItem(above); int aboveId = aboveItem->data(Qt::UserRole).toInt(); QString newBelowName = gui_command_utils::makeInstrumentListText(belowId, aboveName); aboveItem->setText(newBelowName); aboveItem->setData(Qt::UserRole, belowId); QString newAboveName = gui_command_utils::makeInstrumentListText(aboveId, belowName); belowItem->setText(newAboveName); belowItem->setData(Qt::UserRole, aboveId); list_->insertItem(above, belowItem); list_->insertItem(below, aboveItem); editorMan_.lock()->swap(aboveId, belowId); ptn_->onPatternDataGlobalChanged(); } BambooTracker-0.6.5/BambooTracker/gui/command/instrument/swap_instruments_qt_command.hpp000066400000000000000000000040121476276175200317400ustar00rootroot00000000000000/* * Copyright (C) 2020-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef SWAP_INSTRUMENTS_QT_COMMAND_HPP #define SWAP_INSTRUMENTS_QT_COMMAND_HPP #include #include #include #include #include "gui/instrument_editor/instrument_editor_manager.hpp" class PatternEditor; class SwapInstrumentsQtCommand final : public QUndoCommand { public: SwapInstrumentsQtCommand(QListWidget *list, int inst1Row, int inst2Row, const QString& inst1Name, const QString& inst2Name, std::weak_ptr editorMan, PatternEditor* pattern, QUndoCommand* parent = nullptr); void undo() override; void redo() override; int id() const override; private: QListWidget* list_; PatternEditor* ptn_; int inst1Row_, inst2Row_; std::weak_ptr editorMan_; QString inst1Name_, inst2Name_; void swap(int above, int below, QString aboveName, QString belowName); }; #endif // SWAP_INSTRUMENTS_QT_COMMAND_HPP BambooTracker-0.6.5/BambooTracker/gui/command/order/000077500000000000000000000000001476276175200223065ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/gui/command/order/order_commands_qt.hpp000066400000000000000000000041641476276175200265240ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef ORDER_COMMANDS_QT_HPP #define ORDER_COMMANDS_QT_HPP #include "order_list_common_qt_command.hpp" using CloneOrderQtCommand = gui_command_impl::OrderListCommonQtCommandRedrawAll; using ClonePatternsQtCommand = gui_command_impl::OrderListCommonQtCommandRedrawText; using DeleteOrderQtCommand = gui_command_impl::OrderListCommonQtCommandRedrawAll; using DuplicateOrderQtCommand = gui_command_impl::OrderListCommonQtCommandRedrawAll; using InsertOrderBelowQtCommand = gui_command_impl::OrderListCommonQtCommandRedrawAll; using MoveOrderQtCommand = gui_command_impl::OrderListCommonQtCommandRedrawText; using PasteCopiedDataToOrderQtCommand = gui_command_impl::OrderListCommonQtCommandRedrawText; using SetPatternToOrderQtCommand = gui_command_impl::OrderListEntryQtCommandRedrawText; #endif // ORDER_COMMANDS_QT_HPP BambooTracker-0.6.5/BambooTracker/gui/command/order/order_list_common_qt_command.cpp000066400000000000000000000047041476276175200307370ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "order_list_common_qt_command.hpp" #include "gui/order_list_editor/order_list_panel.hpp" namespace gui_command_impl { OrderListCommonQtCommand::OrderListCommonQtCommand( CommandId id, OrderListPanel* panel, bool orderLengthChanged, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel), id_(id), orderLenChanged_(orderLengthChanged) { } void OrderListCommonQtCommand::redo() { panel_->onOrderEdited(); panel_->redrawByPatternChanged(orderLenChanged_); } void OrderListCommonQtCommand::undo() { panel_->onOrderEdited(); panel_->redrawByPatternChanged(orderLenChanged_); } int OrderListCommonQtCommand::id() const { return id_; } OrderListEntryQtCommand::OrderListEntryQtCommand( CommandId id, OrderListPanel* panel, bool redrawAll, const OrderPosition& pos, bool secondEntry, QUndoCommand* parent) : OrderListCommonQtCommand(id, panel, redrawAll, parent), pos_(pos), isSecondEntry_(secondEntry) { } void OrderListEntryQtCommand::undo() { OrderListCommonQtCommand::undo(); panel_->resetEntryCount(); } bool OrderListEntryQtCommand::mergeWith(const QUndoCommand* other) { if (other->id() == id() && !isSecondEntry_) { auto com = dynamic_cast(other); if (com->pos_ == pos_ && com->isSecondEntry_) { isSecondEntry_ = true; redo(); return true; } } isSecondEntry_ = true; return false; } } BambooTracker-0.6.5/BambooTracker/gui/command/order/order_list_common_qt_command.hpp000066400000000000000000000062221476276175200307410ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef ORDER_LIST_COMMON_QT_COMMAND_HPP #define ORDER_LIST_COMMON_QT_COMMAND_HPP #include #include "command/command_id.hpp" #include "gui/order_list_editor/order_position.hpp" class OrderListPanel; namespace gui_command_impl { class OrderListCommonQtCommand : public QUndoCommand { public: virtual void redo() override; virtual void undo() override; int id() const override final; protected: OrderListPanel* panel_; OrderListCommonQtCommand(CommandId id, OrderListPanel* panel, bool redrawAll, QUndoCommand* parent); private: CommandId id_; bool orderLenChanged_; }; template class OrderListCommonQtCommandRedraw final : public OrderListCommonQtCommand { public: OrderListCommonQtCommandRedraw(OrderListPanel* panel, QUndoCommand* parent = nullptr) : OrderListCommonQtCommand(comId, panel, redrawAll, parent) {} }; template using OrderListCommonQtCommandRedrawAll = OrderListCommonQtCommandRedraw; template using OrderListCommonQtCommandRedrawText = OrderListCommonQtCommandRedraw; class OrderListEntryQtCommand : public OrderListCommonQtCommand { public: void undo() override; bool mergeWith(const QUndoCommand* other) override; protected: OrderListEntryQtCommand(CommandId id, OrderListPanel* panel, bool redrawAll, const OrderPosition& pos, bool secondEntry, QUndoCommand* parent); private: const OrderPosition pos_; bool isSecondEntry_; }; template class OrderListEntryQtCommandRedraw final : public OrderListEntryQtCommand { public: OrderListEntryQtCommandRedraw(OrderListPanel* panel, const OrderPosition& pos, bool secondEntry, QUndoCommand* parent = nullptr) : OrderListEntryQtCommand(comId, panel, redrawAll, pos, secondEntry, parent) {} }; template using OrderListEntryQtCommandRedrawAll = OrderListEntryQtCommandRedraw; template using OrderListEntryQtCommandRedrawText = OrderListEntryQtCommandRedraw; } #endif // ORDER_LIST_COMMON_QT_COMMAND_HPP BambooTracker-0.6.5/BambooTracker/gui/command/pattern/000077500000000000000000000000001476276175200226505ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/gui/command/pattern/pattern_commands_qt.hpp000066400000000000000000000111031476276175200274170ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef PATTERN_COMMANDS_QT_HPP #define PATTERN_COMMANDS_QT_HPP #include "pattern_editor_common_qt_command.hpp" using ChangeValuesInPatternQtCommand = gui_command_impl::PatternEditorCommonQtCommandRedrawAll; using DeletePreviousStepQtCommand = gui_command_impl::PatternEditorCommonQtCommandRedrawAll; using EraseCellsInPatternQtCommand = gui_command_impl::PatternEditorCommonQtCommandRedrawAll; using EraseEffectInStepQtCommand = gui_command_impl::PatternEditorCommonQtCommandRedrawAll; using EraseEffectValueInStepQtCommand = gui_command_impl::PatternEditorCommonQtCommandRedrawAll; using EraseInstrumentInStepQtCommand = gui_command_impl::PatternEditorCommonQtCommandRedrawText; using EraseStepQtCommand = gui_command_impl::PatternEditorCommonQtCommandRedrawAll; using EraseVolumeInStepQtCommand = gui_command_impl::PatternEditorCommonQtCommandRedrawText; using ExpandPatternQtCommand = gui_command_impl::PatternEditorCommonQtCommandRedrawAll; using InsertStepQtCommand = gui_command_impl::PatternEditorCommonQtCommandRedrawAll; using InterpolatePatternQtCommand = gui_command_impl::PatternEditorCommonQtCommandRedrawText; using PasteCopiedDataToPatternQtCommand = gui_command_impl::PatternEditorCommonQtCommandRedrawAll; using PasteInsertCopiedDataToPatternQtCommand = gui_command_impl::PatternEditorCommonQtCommandRedrawAll; using PasteMixCopiedDataToPatternQtCommand = gui_command_impl::PatternEditorCommonQtCommandRedrawAll; using PasteOverwriteCopiedDataToPatternQtCommand = gui_command_impl::PatternEditorCommonQtCommandRedrawAll; using ReplaceInstrumentInPatternQtCommand = gui_command_impl::PatternEditorCommonQtCommandRedrawText; using ReversePatternQtCommand = gui_command_impl::PatternEditorCommonQtCommandRedrawAll; using SetEchoBufferAccessQtCommand = gui_command_impl::PatternEditorCommonQtCommandRedrawText; using SetEffectIDToStepQtCommand = gui_command_impl::PatternEditorEntryQtCommandRedrawAll; using SetEffectValueToStepQtCommand = gui_command_impl::PatternEditorEntryQtCommandRedrawText; using SetInstrumentToStepQtCommand = gui_command_impl::PatternEditorEntryQtCommandRedrawText; using SetKeyOffToStepQtCommand = gui_command_impl::PatternEditorCommonQtCommandRedrawText; using SetKeyCutToStepQtCommand = gui_command_impl::PatternEditorCommonQtCommandRedrawText; using SetKeyOnToStepQtCommand = gui_command_impl::PatternEditorCommonQtCommandRedrawText; using SetVolumeToStepQtCommand = gui_command_impl::PatternEditorEntryQtCommandRedrawText; using ShrinkPatternQtCommand = gui_command_impl::PatternEditorCommonQtCommandRedrawAll; using TransposeNoteInPatternQtCommand = gui_command_impl::PatternEditorCommonQtCommandRedrawText; #endif // PATTERN_COMMANDS_QT_HPP BambooTracker-0.6.5/BambooTracker/gui/command/pattern/pattern_editor_common_qt_command.cpp000066400000000000000000000046531476276175200321610ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "pattern_editor_common_qt_command.hpp" #include "gui/pattern_editor/pattern_editor_panel.hpp" namespace gui_command_impl { PatternEditorCommonQtCommand::PatternEditorCommonQtCommand( CommandId id, PatternEditorPanel* panel, bool redrawAll, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel), id_(id), redrawAll_(redrawAll) { } void PatternEditorCommonQtCommand::redo() { panel_->redrawByPatternChanged(redrawAll_); } void PatternEditorCommonQtCommand::undo() { panel_->redrawByPatternChanged(redrawAll_); } int PatternEditorCommonQtCommand::id() const { return id_; } PatternEditorEntryQtCommand::PatternEditorEntryQtCommand( CommandId id, PatternEditorPanel* panel, bool redrawAll, const PatternPosition& pos, bool secondEntry, QUndoCommand* parent) : PatternEditorCommonQtCommand(id, panel, redrawAll, parent), pos_(pos), isSecondEntry_(secondEntry) { } void PatternEditorEntryQtCommand::undo() { PatternEditorCommonQtCommand::undo(); panel_->resetEntryCount(); } bool PatternEditorEntryQtCommand::mergeWith(const QUndoCommand* other) { if (other->id() == id() && !isSecondEntry_) { auto com = dynamic_cast(other); if (com->pos_ == pos_ && com->isSecondEntry_) { isSecondEntry_ = true; redo(); return true; } } isSecondEntry_ = true; return false; } } BambooTracker-0.6.5/BambooTracker/gui/command/pattern/pattern_editor_common_qt_command.hpp000066400000000000000000000064131476276175200321620ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef PATTERN_EDITOR_COMMON_QT_COMMAND_HPP #define PATTERN_EDITOR_COMMON_QT_COMMAND_HPP #include #include "command/command_id.hpp" #include "gui/pattern_editor/pattern_position.hpp" class PatternEditorPanel; namespace gui_command_impl { class PatternEditorCommonQtCommand : public QUndoCommand { public: virtual void redo() override; virtual void undo() override; int id() const override final; protected: PatternEditorPanel* panel_; PatternEditorCommonQtCommand(CommandId id, PatternEditorPanel* panel, bool redrawAll, QUndoCommand* parent); private: CommandId id_; bool redrawAll_; }; template class PatternEditorCommonQtCommandRedraw final : public PatternEditorCommonQtCommand { public: PatternEditorCommonQtCommandRedraw(PatternEditorPanel* panel, QUndoCommand* parent = nullptr) : PatternEditorCommonQtCommand(comId, panel, redrawAll, parent) {} }; template using PatternEditorCommonQtCommandRedrawAll = PatternEditorCommonQtCommandRedraw; template using PatternEditorCommonQtCommandRedrawText = PatternEditorCommonQtCommandRedraw; class PatternEditorEntryQtCommand : public PatternEditorCommonQtCommand { public: void undo() override; bool mergeWith(const QUndoCommand* other) override; protected: PatternEditorEntryQtCommand(CommandId id, PatternEditorPanel* panel, bool redrawAll, const PatternPosition& pos, bool secondEntry, QUndoCommand* parent); private: const PatternPosition pos_; bool isSecondEntry_; }; template class PatternEditorEntryQtCommandRedraw final : public PatternEditorEntryQtCommand { public: PatternEditorEntryQtCommandRedraw(PatternEditorPanel* panel, const PatternPosition& pos, bool secondEntry, QUndoCommand* parent = nullptr) : PatternEditorEntryQtCommand(comId, panel, redrawAll, pos, secondEntry, parent) {} }; template using PatternEditorEntryQtCommandRedrawAll = PatternEditorEntryQtCommandRedraw; template using PatternEditorEntryQtCommandRedrawText = PatternEditorEntryQtCommandRedraw; } #endif // PATTERN_EDITOR_COMMON_QT_COMMAND_HPP BambooTracker-0.6.5/BambooTracker/gui/command_result_message_box.hpp000066400000000000000000000015011476276175200256530ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2025 Rerrah * SPDX-License-Identifier: MIT */ #pragma once #include namespace command_result_message_box { inline void showCommandInvokingErrorMessageBox(QWidget* parent) { QMessageBox::critical(parent, QMessageBox::tr("Error"), QMessageBox::tr("Failed to execute the command."), QMessageBox::Ok, QMessageBox::Ok); } inline void showCommandRedoingErrorMessageBox(QWidget* parent) { QMessageBox::critical(parent, QMessageBox::tr("Error"), QMessageBox::tr("Failed to redo the command."), QMessageBox::Ok, QMessageBox::Ok); } inline void showCommandUndoingErrorMessageBox(QWidget* parent) { QMessageBox::critical(parent, QMessageBox::tr("Error"), QMessageBox::tr("Failed to undo the command."), QMessageBox::Ok, QMessageBox::Ok); } } BambooTracker-0.6.5/BambooTracker/gui/comment_edit_dialog.cpp000066400000000000000000000032711476276175200242520ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "comment_edit_dialog.hpp" #include "ui_comment_edit_dialog.h" CommentEditDialog::CommentEditDialog(QString comment, QWidget *parent) : QDialog(parent), ui(new Ui::CommentEditDialog) { ui->setupUi(this); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); ui->plainTextEdit->setPlainText(comment); } CommentEditDialog::~CommentEditDialog() { delete ui; } void CommentEditDialog::setComment(QString text) { ui->plainTextEdit->setPlainText(text); } void CommentEditDialog::on_plainTextEdit_textChanged() { emit commentChanged(ui->plainTextEdit->toPlainText()); } BambooTracker-0.6.5/BambooTracker/gui/comment_edit_dialog.hpp000066400000000000000000000031751476276175200242620ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef COMMENT_EDIT_DIALOG_HPP #define COMMENT_EDIT_DIALOG_HPP #include #include namespace Ui { class CommentEditDialog; } class CommentEditDialog : public QDialog { Q_OBJECT public: explicit CommentEditDialog(QString comment = "", QWidget *parent = nullptr); ~CommentEditDialog() override; void setComment(QString text); signals: void commentChanged(const QString& text); private: Ui::CommentEditDialog *ui; private slots: void on_plainTextEdit_textChanged(); }; #endif // COMMENT_EDIT_DIALOG_HPP BambooTracker-0.6.5/BambooTracker/gui/comment_edit_dialog.ui000066400000000000000000000027441476276175200241110ustar00rootroot00000000000000 CommentEditDialog 0 0 400 300 Module Comment Qt::Horizontal QDialogButtonBox::Ok buttonBox accepted() CommentEditDialog accept() 248 254 157 274 buttonBox rejected() CommentEditDialog reject() 316 260 286 274 BambooTracker-0.6.5/BambooTracker/gui/configuration_dialog.cpp000066400000000000000000001320021476276175200244450ustar00rootroot00000000000000/* * Copyright (C) 2018-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "configuration_dialog.hpp" #include "ui_configuration_dialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "chip/opna.hpp" #include "audio/audio_stream.hpp" #include "midi/midi.hpp" #include "jamming.hpp" #include "gui/slider_style.hpp" #include "gui/fm_envelope_set_edit_dialog.hpp" #include "gui/note_name_manager.hpp" #include "gui/gui_utils.hpp" namespace { inline Qt::CheckState toCheckState(bool enabled) { return enabled ? Qt::Checked : Qt::Unchecked; } inline bool fromCheckState(Qt::CheckState state) { return (state == Qt::Checked) ? true : false; } struct NotationSystemAttribute { const QString name; const char* dispName; const NoteNotationSystem ev; }; const NotationSystemAttribute NOTATION_SYSS[] = { { "English", QT_TRANSLATE_NOOP("NotationSystem", "English"), NoteNotationSystem::ENGLISH }, { "German", QT_TRANSLATE_NOOP("NotationSystem", "German"), NoteNotationSystem::GERMAN } }; } ConfigurationDialog::ConfigurationDialog(std::weak_ptr config, std::weak_ptr palette, std::weak_ptr stream, QWidget *parent) : QDialog(parent), ui(new Ui::ConfigurationDialog), config_(config), refPalette_(palette), stream_(stream) { ui->setupUi(this); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); QObject::connect(ui->buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, [&] { on_ConfigurationDialog_accepted(); emit applyPressed(); }); std::shared_ptr configLocked = config.lock(); // General // // General settings auto glfunc = [&](int i, bool enabled, QString desc) { QListWidgetItem* item = ui->generalSettingsListWidget->item(i); item->setCheckState(toCheckState(enabled)); item->setData(Qt::UserRole, desc); }; glfunc(0, configLocked->getWarpCursor(), tr("Wrap the cursor around the edges of the pattern editor.")); glfunc(1, configLocked->getWarpAcrossOrders(), tr("Move to the previous or next order when reaching the top or bottom in the pattern editor.")); glfunc(2, configLocked->getShowRowNumberInHex(), tr("Display row numbers and the playback position on the status bar in hexadecimal.")); glfunc(3, configLocked->getShowPreviousNextOrders(), tr("Preview the previous and next orders in the pattern editor.")); glfunc(4, configLocked->getBackupModules(), tr("Create a backup copy of the existing file when saving a module.")); glfunc(5, configLocked->getDontSelectOnDoubleClick(), tr("Don't select the whole track when double-clicking in the pattern editor.")); glfunc(6, configLocked->getReverseFMVolumeOrder(), tr("Reverse the order of FM volume so that 00 is the quietest in the pattern editor.")); glfunc(7, configLocked->getMoveCursorToRight(), tr("Automatically move the cursor right after entering effects in the pattern editor.")); glfunc(8, configLocked->getRetrieveChannelState(), tr("Reconstruct the current channel's state from previous orders upon playing.")); glfunc(9, configLocked->getEnableTranslation(), tr("Translate to your language from the next launch. See readme to check supported languages.")); glfunc(10, configLocked->getShowFMDetuneAsSigned(), tr("Display FM detune values as signed numbers in the FM envelope editor.")); glfunc(11, configLocked->getFill00ToEffectValue(), tr("Automatically fill 00 to the effect value column upon entering an effect ID.")); glfunc(12, configLocked->getMoveCursorByHorizontalScroll(), tr("Move the cursor with the horizontal scroll bar in the order list and the pattern editor.")); glfunc(13, configLocked->getOverwriteUnusedUneditedPropety(), tr("Overwrite unused and unedited instrument properties when creating new properties. " "If disabled, override unused properties regardless of editing.")); glfunc(14, configLocked->getWriteOnlyUsedSamples(), tr("Only send samples used by instruments to the ADPCM memory. " "It is recommended to turn this off if you change ADPCM samples frequently due to the high cost of rewriting..")); glfunc(15, configLocked->getReflectInstrumentNumberChange(), tr("Automatically update the instrument number in patterns when an instrument's number is changed.")); glfunc(16, configLocked->getFixJammingVolume(), tr("Set maximum volume during jam mode. When unchecked, the volume is changed by the volume spinbox.")); glfunc(17, configLocked->getMuteHiddenTracks(), tr("Automatically mute tracks when they are hidden.")); glfunc(18, configLocked->getRestoreTrackVisibility(), tr("Restore the previous track visibility on startup.")); glfunc(19, configLocked->getOverflowPaste(), tr("Move pasted pattern data outside the rows of the current frame to subsequent frames.")); // Edit settings ui->pageJumpLengthSpinBox->setValue(static_cast(configLocked->getPageJumpLength())); // Wave view ui->waveViewRateSpinBox->setValue(configLocked->getWaveViewFrameRate()); // Note names { QSignalBlocker blocker(ui->noteNameComboBox); for (const auto& attrib : NOTATION_SYSS) { ui->noteNameComboBox->addItem(QCoreApplication::translate("NotationSystem", attrib.dispName)); if (attrib.ev == configLocked->getNotationSystem()) { ui->noteNameComboBox->setCurrentIndex(ui->noteNameComboBox->count() - 1); } } } // Keys ui->shortcutsTreeWidget->header()->setSectionResizeMode(0, QHeaderView::Stretch); ui->shortcutsTreeWidget->header()->setSectionResizeMode(1, QHeaderView::Fixed); struct ActionPair { Configuration::ShortcutAction action; QString text; }; const ActionPair shortcutsActions[] = { { Configuration::ShortcutAction::KeyOff, tr("Key release") }, { Configuration::ShortcutAction::KeyCut, tr("Key cut") }, { Configuration::ShortcutAction::OctaveUp, tr("Octave up") }, { Configuration::ShortcutAction::OctaveDown, tr("Octave down") }, { Configuration::ShortcutAction::EchoBuffer, tr("Echo buffer") }, { Configuration::ShortcutAction::PlayAndStop, tr("Play and stop") }, { Configuration::ShortcutAction::Play, tr("Play") }, { Configuration::ShortcutAction::PlayFromStart, tr("Play from start") }, { Configuration::ShortcutAction::PlayPattern, tr("Play pattern") }, { Configuration::ShortcutAction::PlayFromCursor, tr("Play from cursor") }, { Configuration::ShortcutAction::PlayFromMarker, tr("Play from marker") }, { Configuration::ShortcutAction::PlayStep, tr("Play step") }, { Configuration::ShortcutAction::Stop, tr("Stop") }, { Configuration::ShortcutAction::FocusOnPattern, tr("Focus on pattern editor") }, { Configuration::ShortcutAction::FocusOnOrder, tr("Focus on order list") }, { Configuration::ShortcutAction::FocusOnInstrument, tr("Focus on instrument list") }, { Configuration::ShortcutAction::ToggleEditJam, tr("Toggle edit/jam mode") }, { Configuration::ShortcutAction::SetMarker, tr("Set marker") }, { Configuration::ShortcutAction::PasteMix, tr("Paste and mix") }, { Configuration::ShortcutAction::PasteOverwrite, tr("Paste and overwrite") }, { Configuration::ShortcutAction::PasteInsert, tr("Paste and insert") }, { Configuration::ShortcutAction::SelectAll, tr("Select all") }, { Configuration::ShortcutAction::Deselect, tr("Deselect") }, { Configuration::ShortcutAction::SelectRow, tr("Select row") }, { Configuration::ShortcutAction::SelectColumn, tr("Select column") }, { Configuration::ShortcutAction::SelectPattern, tr("Select pattern") }, { Configuration::ShortcutAction::SelectOrder, tr("Select order") }, { Configuration::ShortcutAction::GoToStep, tr("Go to step") }, { Configuration::ShortcutAction::ToggleTrack, tr("Toggle track") }, { Configuration::ShortcutAction::SoloTrack, tr("Solo track") }, { Configuration::ShortcutAction::Interpolate, tr("Interpolate") }, { Configuration::ShortcutAction::Reverse, tr("Reverse") }, { Configuration::ShortcutAction::GoToPrevOrder, tr("Go to previous order") }, { Configuration::ShortcutAction::GoToNextOrder, tr("Go to next order") }, { Configuration::ShortcutAction::ToggleBookmark, tr("Toggle bookmark") }, { Configuration::ShortcutAction::PrevBookmark, tr("Previous bookmark") }, { Configuration::ShortcutAction::NextBookmark, tr("Next bookmark") }, { Configuration::ShortcutAction::DecreaseNote, tr("Transpose down one semitone") }, { Configuration::ShortcutAction::IncreaseNote, tr("Transpose up one semitone") }, { Configuration::ShortcutAction::DecreaseOctave, tr("Transpose down one octave") }, { Configuration::ShortcutAction::IncreaseOctave, tr("Transpose up one octave") }, { Configuration::ShortcutAction::PrevInstrument, tr("Previous instrument") }, { Configuration::ShortcutAction::NextInstrument, tr("Next instrument") }, { Configuration::ShortcutAction::MaskInstrument, tr("Mask instrument") }, { Configuration::ShortcutAction::MaskVolume, tr("Mask volume") }, { Configuration::ShortcutAction::EditInstrument, tr("Edit instrument") }, { Configuration::ShortcutAction::FollowMode, tr("Follow mode") }, { Configuration::ShortcutAction::DuplicateOrder, tr("Duplicate order") }, { Configuration::ShortcutAction::ClonePatterns, tr("Clone patterns") }, { Configuration::ShortcutAction::CloneOrder, tr("Clone order") }, { Configuration::ShortcutAction::ReplaceInstrument, tr("Replace instrument") }, { Configuration::ShortcutAction::ExpandPattern, tr("Expand pattern") }, { Configuration::ShortcutAction::ShrinkPattern, tr("Shrink pattern") }, { Configuration::ShortcutAction::FineDecreaseValues, tr("Fine decrease values") }, { Configuration::ShortcutAction::FineIncreaseValues, tr("Fine increase values") }, { Configuration::ShortcutAction::CoarseDecreaseValues, tr("Coarse decrease values") }, { Configuration::ShortcutAction::CoarseIncreaseValuse, tr("Coarse increase valuse") }, { Configuration::ShortcutAction::ExpandEffect, tr("Expand effect column") }, { Configuration::ShortcutAction::ShrinkEffect, tr("Shrink effect column") }, { Configuration::ShortcutAction::PrevHighlighted, tr("Previous highlighted step") }, { Configuration::ShortcutAction::NextHighlighted, tr("Next highlighted step") }, { Configuration::ShortcutAction::IncreasePatternSize, tr("Increase pattern size") }, { Configuration::ShortcutAction::DecreasePatternSize, tr("Decrease pattern size") }, { Configuration::ShortcutAction::IncreaseEditStep, tr("Increase edit step") }, { Configuration::ShortcutAction::DecreaseEditStep, tr("Decrease edit step") }, { Configuration::ShortcutAction::DisplayEffectList, tr("Display effect list") }, { Configuration::ShortcutAction::PreviousSong, tr("Previous song") }, { Configuration::ShortcutAction::NextSong, tr("Next song") }, { Configuration::ShortcutAction::JamVolumeUp, tr("Jam volume up") }, { Configuration::ShortcutAction::JamVolumeDown, tr("Jam volume down") } }; std::unordered_map shortcuts = configLocked->getShortcuts(); for (const auto& pair : shortcutsActions) { int row = ui->shortcutsTreeWidget->topLevelItemCount(); auto item = new QTreeWidgetItem(); item->setText(0, pair.text); ui->shortcutsTreeWidget->insertTopLevelItem(row, item); auto widget = new QWidget(); widget->setLayout(new QHBoxLayout()); auto seq = new QKeySequenceEdit(gui_utils::utf8ToQString(shortcuts.at(pair.action))); shortcutsMap_[pair.action] = seq; auto button = new QToolButton(); button->setIcon(QIcon(":/icon/remove_inst")); QObject::connect(button, &QToolButton::clicked, seq, &QKeySequenceEdit::clear); auto layout = widget->layout(); layout->setSpacing(0); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(seq); layout->addWidget(button); ui->shortcutsTreeWidget->setItemWidget(item, 1, widget); } ui->keyboardTypeComboBox->setCurrentIndex(static_cast(configLocked->getNoteEntryLayout())); customLayoutKeysMap_ = { { JamKey::LowC, ui->lowCEdit }, { JamKey::LowCS, ui->lowCSEdit }, { JamKey::LowD, ui->lowDEdit }, { JamKey::LowDS, ui->lowDSEdit }, { JamKey::LowE, ui->lowEEdit }, { JamKey::LowF, ui->lowFEdit }, { JamKey::LowFS, ui->lowFSEdit }, { JamKey::LowG, ui->lowGEdit }, { JamKey::LowGS, ui->lowGSEdit }, { JamKey::LowA, ui->lowAEdit }, { JamKey::LowAS, ui->lowASEdit }, { JamKey::LowB, ui->lowBEdit }, { JamKey::LowC2, ui->lowHighCEdit }, { JamKey::LowCS2, ui->lowHighCSEdit }, { JamKey::LowD2, ui->lowHighDEdit }, { JamKey::HighC, ui->highCEdit }, { JamKey::HighCS, ui->highCSEdit }, { JamKey::HighD, ui->highDEdit }, { JamKey::HighDS, ui->highDSEdit }, { JamKey::HighE, ui->highEEdit }, { JamKey::HighF, ui->highFEdit }, { JamKey::HighFS, ui->highFSEdit }, { JamKey::HighG, ui->highGEdit }, { JamKey::HighGS, ui->highGSEdit }, { JamKey::HighA, ui->highAEdit }, { JamKey::HighAS, ui->highASEdit }, { JamKey::HighB, ui->highBEdit }, { JamKey::HighC2, ui->highHighCEdit }, { JamKey::HighCS2, ui->highHighCSEdit }, { JamKey::HighD2, ui->highHighDEdit } }; for (const auto& pair : configLocked->getCustomLayoutKeys()) { customLayoutKeysMap_.at(pair.second)->setKeySequence(QKeySequence(gui_utils::utf8ToQString(pair.first))); } updateNoteNames(); // Sound // ui->emulatorComboBox->addItem("MAME YM2608", static_cast(chip::OpnaEmulator::Mame)); ui->emulatorComboBox->addItem("Nuked OPN-Mod", static_cast(chip::OpnaEmulator::Nuked)); ui->emulatorComboBox->addItem("ymfm", static_cast(chip::OpnaEmulator::Ymfm)); ui->emulatorComboBox->setCurrentIndex(ui->emulatorComboBox->findData(configLocked->getEmulator())); ui->zeroWaitWriteCheckBox->setChecked(configLocked->getImmediateWriteModeEnabled()); { QSignalBlocker blocker(ui->audioApiComboBox); int sndApiRow = -1; int defSndApiRow = 0; for (auto& name : stream.lock()->getAvailableBackends()) { ui->audioApiComboBox->addItem(name); if (name == QString::fromStdString(configLocked->getSoundAPI())) sndApiRow = ui->audioApiComboBox->count() - 1; if (name == stream.lock()->getCurrentBackend()) defSndApiRow = sndApiRow = ui->audioApiComboBox->count() - 1; } ui->audioApiComboBox->setCurrentIndex((sndApiRow == -1) ? defSndApiRow : sndApiRow); } on_audioApiComboBox_currentIndexChanged(ui->audioApiComboBox->currentText()); ui->realChipComboBox->addItem(tr("None"), static_cast(RealChipInterfaceType::NONE)); #ifdef USE_REAL_CHIP ui->realChipComboBox->addItem("SCCI", static_cast(RealChipInterfaceType::SCCI)); ui->realChipComboBox->addItem("C86CTL", static_cast(RealChipInterfaceType::C86CTL)); #endif switch (configLocked->getRealChipInterface()) { default: // Fall through case RealChipInterfaceType::NONE: ui->realChipComboBox->setCurrentIndex(0); break; case RealChipInterfaceType::SCCI: ui->realChipComboBox->setCurrentIndex(1); break; case RealChipInterfaceType::C86CTL: ui->realChipComboBox->setCurrentIndex(2); break; } { QSignalBlocker blocker1(ui->midiInputDeviceComboBox), blocker2(ui->midiApiComboBox); MidiInterface& midiIntf = MidiInterface::getInstance(); int midiApiRow = -1; int defMidiApiRow = 0; for (auto& name : midiIntf.getAvailableApis()) { ui->midiApiComboBox->addItem(gui_utils::utf8ToQString(name)); if (name == configLocked->getMidiAPI()) midiApiRow = ui->midiApiComboBox->count() - 1; if (name == midiIntf.currentApiName()) defMidiApiRow = midiApiRow = ui->midiApiComboBox->count() - 1; } ui->midiApiComboBox->setCurrentIndex((midiApiRow == -1) ? defMidiApiRow : midiApiRow); } onMidiApiChanged(ui->midiApiComboBox->currentText(), false); ui->midiInputGroupBox->setChecked(configLocked->getMidiEnabled()); constexpr uint32_t SAMPLE_RATES[] = { 8000, 16000, 22050, 32000, 44100, 48000, 55466, 96000, 192000, 249600 }; constexpr size_t N_SAMPLE_RATES = sizeof(SAMPLE_RATES) / sizeof(SAMPLE_RATES[0]); for (auto rate : SAMPLE_RATES) { ui->sampleRateComboBox->addItem(QString("%1Hz").arg(rate), rate); } ui->sampleRateComboBox->setCurrentIndex(0); for (size_t i = 0; i < N_SAMPLE_RATES; ++i) { if (SAMPLE_RATES[i] == configLocked->getSampleRate()) { ui->sampleRateComboBox->setCurrentIndex(i); } } ui->resamplerComboBox->addItem(QString("Linear (%1)").arg(tr("old, deprecated")), static_cast(chip::ResamplerType::Linear)); ui->resamplerComboBox->addItem("blip_buf", static_cast(chip::ResamplerType::BlipBuf)); ui->resamplerComboBox->addItem(QString("blip_buf (%1)").arg(tr("fast")), static_cast(chip::ResamplerType::FastBlipBuf)); for (int i = 0; i < ui->resamplerComboBox->count(); ++i) { if (static_cast(ui->resamplerComboBox->itemData(i).toInt()) == configLocked->getResamplerType()) { ui->resamplerComboBox->setCurrentIndex(i); break; } } ui->bufferLengthHorizontalSlider->setStyle(SliderStyle::instance()); QObject::connect(ui->bufferLengthHorizontalSlider, &QSlider::valueChanged, this, [&](int value) { ui->bufferLengthLabel->setText(QString::number(value) + "ms"); }); ui->bufferLengthHorizontalSlider->setValue(static_cast(configLocked->getBufferLength())); // Mixer // ui->masterMixerSlider->setText(tr("Master")); ui->masterMixerSlider->setSuffix("%"); ui->masterMixerSlider->setMaximum(200); ui->masterMixerSlider->setMinimum(0); ui->masterMixerSlider->setTickPosition(QSlider::TicksBothSides); ui->masterMixerSlider->setTickInterval(20); ui->masterMixerSlider->setValue(configLocked->getMixerVolumeMaster()); ui->fmMixerSlider->setText("FM"); ui->fmMixerSlider->setSuffix("dB"); ui->fmMixerSlider->setMaximum(120); ui->fmMixerSlider->setMinimum(-120); ui->fmMixerSlider->setValueRate(0.1); ui->fmMixerSlider->setSign(true); ui->fmMixerSlider->setTickPosition(QSlider::TicksBothSides); ui->fmMixerSlider->setTickInterval(20); ui->fmMixerSlider->setValue(static_cast(configLocked->getMixerVolumeFM() * 10)); ui->ssgMixerSlider->setText("SSG"); ui->ssgMixerSlider->setSuffix("dB"); ui->ssgMixerSlider->setMaximum(120); ui->ssgMixerSlider->setMinimum(-120); ui->ssgMixerSlider->setValueRate(0.1); ui->ssgMixerSlider->setSign(true); ui->ssgMixerSlider->setTickPosition(QSlider::TicksBothSides); ui->ssgMixerSlider->setTickInterval(20); ui->ssgMixerSlider->setValue(static_cast(configLocked->getMixerVolumeSSG() * 10)); // Formats // fmEnvelopeTexts_ = configLocked->getFMEnvelopeTexts(); updateEnvelopeSetUi(); // Appearance // ui->colorsTreeWidget->setColumnWidth(0, 250); updateColorTreeFrom(palette.lock().get()); auto deserializeFont = [](const std::string& fontStr) { QFont font; font.fromString(gui_utils::utf8ToQString(fontStr)); return font; }; ui->ptnHdFontInfoWidget->setFontInfo(deserializeFont(configLocked->getPatternEditorHeaderFont())); ui->ptnRowFontInfoWidget->setFontInfo(deserializeFont(configLocked->getPatternEditorRowsFont())); ui->odrHdFontInfoWidget->setFontInfo(deserializeFont(configLocked->getOrderListHeaderFont())); ui->odrRowFontInfoWidget->setFontInfo(deserializeFont(configLocked->getOrderListRowsFont())); } ConfigurationDialog::~ConfigurationDialog() { delete ui; } void ConfigurationDialog::on_ConfigurationDialog_accepted() { std::shared_ptr configLocked = config_.lock(); // General // // General settings configLocked->setWarpCursor(fromCheckState(ui->generalSettingsListWidget->item(0)->checkState())); configLocked->setWarpAcrossOrders(fromCheckState(ui->generalSettingsListWidget->item(1)->checkState())); configLocked->setShowRowNumberInHex(fromCheckState(ui->generalSettingsListWidget->item(2)->checkState())); configLocked->setShowPreviousNextOrders(fromCheckState(ui->generalSettingsListWidget->item(3)->checkState())); configLocked->setBackupModules(fromCheckState(ui->generalSettingsListWidget->item(4)->checkState())); configLocked->setDontSelectOnDoubleClick(fromCheckState(ui->generalSettingsListWidget->item(5)->checkState())); configLocked->setReverseFMVolumeOrder(fromCheckState(ui->generalSettingsListWidget->item(6)->checkState())); configLocked->setMoveCursorToRight(fromCheckState(ui->generalSettingsListWidget->item(7)->checkState())); configLocked->setRetrieveChannelState(fromCheckState(ui->generalSettingsListWidget->item(8)->checkState())); configLocked->setEnableTranslation(fromCheckState(ui->generalSettingsListWidget->item(9)->checkState())); configLocked->setShowFMDetuneAsSigned(fromCheckState(ui->generalSettingsListWidget->item(10)->checkState())); configLocked->setFill00ToEffectValue(fromCheckState(ui->generalSettingsListWidget->item(11)->checkState())); configLocked->setMoveCursorByHorizontalScroll(fromCheckState(ui->generalSettingsListWidget->item(12)->checkState())); configLocked->setOverwriteUnusedUneditedPropety(fromCheckState(ui->generalSettingsListWidget->item(13)->checkState())); configLocked->setWriteOnlyUsedSamples(fromCheckState(ui->generalSettingsListWidget->item(14)->checkState())); configLocked->setReflectInstrumentNumberChange(fromCheckState(ui->generalSettingsListWidget->item(15)->checkState())); configLocked->setFixJammingVolume(fromCheckState(ui->generalSettingsListWidget->item(16)->checkState())); configLocked->setMuteHiddenTracks(fromCheckState(ui->generalSettingsListWidget->item(17)->checkState())); configLocked->setRestoreTrackVisibility(fromCheckState(ui->generalSettingsListWidget->item(18)->checkState())); configLocked->setOverflowPaste(fromCheckState(ui->generalSettingsListWidget->item(19)->checkState())); // Edit settings configLocked->setPageJumpLength(static_cast(ui->pageJumpLengthSpinBox->value())); // Wave view configLocked->setWaveViewFrameRate(ui->waveViewRateSpinBox->value()); // Note names configLocked->setNotationSystem(NOTATION_SYSS[ui->noteNameComboBox->currentIndex()].ev); // Keys std::unordered_map shortcuts; for (const auto& pair: shortcutsMap_) { shortcuts[pair.first] = pair.second->keySequence().toString().toStdString(); } configLocked->setShortcuts(shortcuts); configLocked->setNoteEntryLayout(static_cast(ui->keyboardTypeComboBox->currentIndex())); std::unordered_map customLayoutNewKeys; for (const auto& pair : customLayoutKeysMap_) { customLayoutNewKeys[pair.second->keySequence().toString().toStdString()] = pair.first; } configLocked->setCustomLayoutKeys(customLayoutNewKeys); // Sound // int emu = ui->emulatorComboBox->currentData().toInt(); bool changedEmu = false; if (emu != configLocked->getEmulator()) { configLocked->setEmulator(emu); changedEmu = true; } configLocked->setImmediateWriteModeEnabled(ui->zeroWaitWriteCheckBox->isChecked()); configLocked->setSoundDevice(ui->audioDeviceComboBox->currentText().toUtf8().toStdString()); configLocked->setSoundAPI(ui->audioApiComboBox->currentText().toUtf8().toStdString()); configLocked->setRealChipInterface(static_cast( ui->realChipComboBox->currentData(Qt::UserRole).toInt())); configLocked->setMidiEnabled(ui->midiInputGroupBox->isChecked()); configLocked->setMidiAPI(ui->midiApiComboBox->currentText().toUtf8().toStdString()); configLocked->setMidiInputPort(ui->midiInputDeviceComboBox->currentData().toString().toUtf8().toStdString()); configLocked->setSampleRate(ui->sampleRateComboBox->currentData(Qt::UserRole).toUInt()); configLocked->setResamplerType(static_cast(ui->resamplerComboBox->currentData().toInt())); configLocked->setBufferLength(static_cast(ui->bufferLengthHorizontalSlider->value())); // Mixer // configLocked->setMixerVolumeMaster(ui->masterMixerSlider->value()); configLocked->setMixerVolumeFM(ui->fmMixerSlider->value() * 0.1); configLocked->setMixerVolumeSSG(ui->ssgMixerSlider->value() * 0.1); // Formats // std::sort(fmEnvelopeTexts_.begin(), fmEnvelopeTexts_.end(), [](const FMEnvelopeText& a, const FMEnvelopeText& b) -> bool { return (a.name < b.name); }); configLocked->setFMEnvelopeTexts(fmEnvelopeTexts_); // Appearance // setPaletteFromColorTree(refPalette_.lock().get()); configLocked->setPatternEditorHeaderFont(ui->ptnHdFontInfoWidget->getFontInfo().toString().toStdString()); configLocked->setPatternEditorRowsFont(ui->ptnRowFontInfoWidget->getFontInfo().toString().toStdString()); configLocked->setOrderListHeaderFont(ui->odrHdFontInfoWidget->getFontInfo().toString().toStdString()); configLocked->setOrderListRowsFont(ui->odrRowFontInfoWidget->getFontInfo().toString().toStdString()); if (changedEmu) { QMessageBox::information(this, tr("Configuration"), tr("The change of emulator will be effective after restarting the program.")); } } /***** General *****/ void ConfigurationDialog::on_generalSettingsListWidget_itemSelectionChanged() { QString text(""); if (QListWidgetItem* item = ui->generalSettingsListWidget->currentItem()) { text = item->data(Qt::UserRole).toString(); } ui->descPlainTextEdit->setPlainText(tr("Description: %1").arg(text)); } void ConfigurationDialog::on_noteNameComboBox_currentIndexChanged(int index) { // Change notation system temporary NoteNameManager& man = NoteNameManager::getManager(); man.setNotationSystem(NOTATION_SYSS[index].ev); updateNoteNames(); // Restore global notation system for (const auto& attrib : NOTATION_SYSS) { if (attrib.ev == config_.lock()->getNotationSystem()) { man.setNotationSystem(attrib.ev); return; } } } /***** Mixer *****/ void ConfigurationDialog::on_mixerResetPushButton_clicked() { ui->fmMixerSlider->setValue(0); ui->ssgMixerSlider->setValue(0); } /***** Sound *****/ void ConfigurationDialog::on_audioApiComboBox_currentIndexChanged(const QString &arg1) { ui->audioDeviceComboBox->clear(); std::vector devices = stream_.lock()->getAvailableDevices(arg1); if (devices.empty()) { ui->audioDeviceComboBox->setEnabled(false); return; } else { ui->audioDeviceComboBox->setEnabled(true); } int devRow = -1; int defDevRow = 0; for (auto& name : devices) { ui->audioDeviceComboBox->addItem(name); if (name == gui_utils::utf8ToQString(config_.lock()->getSoundDevice())) devRow = ui->audioDeviceComboBox->count() - 1; if (name == stream_.lock()->getDefaultOutputDevice(arg1)) defDevRow = ui->audioDeviceComboBox->count() - 1; } ui->audioDeviceComboBox->setCurrentIndex((devRow == -1) ? defDevRow : devRow); } void ConfigurationDialog::on_midiApiComboBox_currentIndexChanged(const QString &arg1) { onMidiApiChanged(arg1); } void ConfigurationDialog::onMidiApiChanged(const QString &arg1, bool hasInitialized) { ui->midiInputDeviceComboBox->clear(); MidiInterface &intf = MidiInterface::getInstance(); std::vector ports; bool vport; std::string apiName = arg1.toStdString(); if (intf.currentApiName() == apiName) { ports = intf.getRealInputPorts(); vport = intf.supportsVirtualPort(); } else { ports = intf.getRealInputPorts(apiName); vport = intf.supportsVirtualPort(apiName); } int devRow = -1; if (vport) { ui->midiInputDeviceComboBox->addItem(tr("Virtual port"), QString()); } else if (ports.empty()) { ui->midiInputDeviceComboBox->setEnabled(false); return; } if (hasInitialized) // To reflect unchecked groupbox when displaying the dialog for the first time ui->midiInputDeviceComboBox->setEnabled(true); for (auto& portName : ports) { auto name = QString::fromStdString(portName); ui->midiInputDeviceComboBox->addItem(name, name); if (portName == config_.lock()->getMidiInputPort()) devRow = ui->midiInputDeviceComboBox->count() - 1; } ui->midiInputDeviceComboBox->setCurrentIndex((devRow == -1) ? 0 : devRow); } /*****Formats *****/ void ConfigurationDialog::on_addEnvelopeSetPushButton_clicked() { auto name = tr("Set %1").arg(fmEnvelopeTexts_.size() + 1); fmEnvelopeTexts_.push_back({ name.toUtf8().toStdString(), std::vector() }); updateEnvelopeSetUi(); for (int i = ui->envelopeTypeListWidget->count() - 1; i >= 0; --i) { if (ui->envelopeTypeListWidget->item(i)->text() == name) { ui->envelopeTypeListWidget->setCurrentRow(i); break; } } } void ConfigurationDialog::on_removeEnvelopeSetpushButton_clicked() { fmEnvelopeTexts_.erase(fmEnvelopeTexts_.begin() + ui->envelopeTypeListWidget->currentRow()); updateEnvelopeSetUi(); } void ConfigurationDialog::on_editEnvelopeSetPushButton_clicked() { size_t row = static_cast(ui->envelopeTypeListWidget->currentRow()); FMEnvelopeSetEditDialog dialog(fmEnvelopeTexts_.at(row).texts, this); dialog.setWindowTitle(dialog.windowTitle() + ": " + ui->envelopeSetNameLineEdit->text()); if (dialog.exec() == QDialog::Accepted) { fmEnvelopeTexts_.at(row).texts = dialog.getSet(); } } void ConfigurationDialog::on_envelopeSetNameLineEdit_textChanged(const QString &arg1) { fmEnvelopeTexts_.at(static_cast(ui->envelopeTypeListWidget->currentRow())).name = arg1.toStdString(); ui->envelopeTypeListWidget->currentItem()->setText(arg1); } void ConfigurationDialog::on_envelopeTypeListWidget_currentRowChanged(int currentRow) { if (currentRow == -1) { ui->editEnvelopeSetPushButton->setEnabled(false); ui->removeEnvelopeSetpushButton->setEnabled(false); ui->envelopeSetNameLineEdit->setEnabled(false); } else { ui->editEnvelopeSetPushButton->setEnabled(true); ui->removeEnvelopeSetpushButton->setEnabled(true); ui->envelopeSetNameLineEdit->setEnabled(true); ui->envelopeSetNameLineEdit->setText(ui->envelopeTypeListWidget->item(currentRow)->text()); } } void ConfigurationDialog::updateEnvelopeSetUi() { std::sort(fmEnvelopeTexts_.begin(), fmEnvelopeTexts_.end(), [](const FMEnvelopeText& a, const FMEnvelopeText& b) -> bool { return (a.name < b.name); }); ui->envelopeTypeListWidget->clear(); for (auto& texts : fmEnvelopeTexts_) ui->envelopeTypeListWidget->addItem(gui_utils::utf8ToQString(texts.name)); } /***** Keys *****/ void ConfigurationDialog::on_keyboardTypeComboBox_currentIndexChanged(int) { bool enableCustomLayoutInterface = ui->keyboardTypeComboBox->currentIndex() == 0; ui->lowHighKeysTabWidget->setEnabled(enableCustomLayoutInterface); ui->customLayoutResetButton->setEnabled(enableCustomLayoutInterface); } void ConfigurationDialog::on_customLayoutResetButton_clicked() { std::unordered_map QWERTYLayoutMapping = config_.lock()->mappingLayouts.at (Configuration::KeyboardLayout::QWERTY); std::unordered_map::const_iterator QWERTYLayoutMappingIterator = QWERTYLayoutMapping.begin(); while (QWERTYLayoutMappingIterator != QWERTYLayoutMapping.end()) { customLayoutKeysMap_.at(QWERTYLayoutMappingIterator->second)->setKeySequence(QKeySequence(QString::fromStdString(QWERTYLayoutMappingIterator->first))); QWERTYLayoutMappingIterator++; } } void ConfigurationDialog::addShortcutItem(QString action, std::string shortcut) { int row = ui->shortcutsTreeWidget->topLevelItemCount(); auto titem = new QTreeWidgetItem(); titem->setText(0, action); ui->shortcutsTreeWidget->insertTopLevelItem(row, titem); ui->shortcutsTreeWidget->setItemWidget(titem, 1, new QKeySequenceEdit(gui_utils::utf8ToQString(shortcut))); } std::string ConfigurationDialog::getShortcutString(int row) const { return qobject_cast( ui->shortcutsTreeWidget->itemWidget(ui->shortcutsTreeWidget->topLevelItem(row), 1) )->keySequence().toString().toStdString(); } void ConfigurationDialog::updateNoteNames() { const NoteNameManager& man = NoteNameManager::getManager(); ui->lowCLabel->setText(man.getNoteName(0)); ui->lowCSLabel->setText(man.getNoteName(1)); ui->lowDLabel->setText(man.getNoteName(2)); ui->lowDSLabel->setText(man.getNoteName(3)); ui->lowELabel->setText(man.getNoteName(4)); ui->lowFLabel->setText(man.getNoteName(5)); ui->lowFSLabel->setText(man.getNoteName(6)); ui->lowGLabel->setText(man.getNoteName(7)); ui->lowGSLabel->setText(man.getNoteName(8)); ui->lowALabel->setText(man.getNoteName(9)); ui->lowASLabel->setText(man.getNoteName(10)); ui->lowBLabel->setText(man.getNoteName(11)); ui->lowHighCLabel->setText(man.getNoteName(0)); ui->lowHighCSLabel->setText(man.getNoteName(1)); ui->lowHighDLabel->setText(man.getNoteName(2)); ui->highCLabel->setText(man.getNoteName(0)); ui->highCSLabel->setText(man.getNoteName(1)); ui->highDLabel->setText(man.getNoteName(2)); ui->highDSLabel->setText(man.getNoteName(3)); ui->highELabel->setText(man.getNoteName(4)); ui->highFLabel->setText(man.getNoteName(5)); ui->highFSLabel->setText(man.getNoteName(6)); ui->highGLabel->setText(man.getNoteName(7)); ui->highGSLabel->setText(man.getNoteName(8)); ui->highALabel->setText(man.getNoteName(9)); ui->highASLabel->setText(man.getNoteName(10)); ui->highBLabel->setText(man.getNoteName(11)); ui->highHighCLabel->setText(man.getNoteName(0)); ui->highHighCSLabel->setText(man.getNoteName(1)); ui->highHighDLabel->setText(man.getNoteName(2)); } /***** Appearance *****/ void ConfigurationDialog::on_colorEditPushButton_clicked() { QTreeWidgetItem* item = ui->colorsTreeWidget->currentItem(); if (item == nullptr || item->parent() == nullptr) return; QColorDialog dialog(item->data(1, Qt::BackgroundRole).value(), this); dialog.setOption(QColorDialog::ShowAlphaChannel); if (dialog.exec() == QDialog::Accepted) item->setData(1, Qt::BackgroundRole, dialog.currentColor()); } void ConfigurationDialog::on_colorLoadPushButton_clicked() { QString file = QFileDialog::getOpenFileName(this, tr("Open color scheme"), QApplication::applicationDirPath() + "/skins", tr("ini file (*.ini)") + ";;" + tr("All files (*)"), nullptr #if defined(Q_OS_LINUX) || (defined(Q_OS_BSD4) && !defined(Q_OS_DARWIN)) , QFileDialog::DontUseNativeDialog #endif ); if (file.isNull()) return; ColorPalette palette; if (io::loadPalette(file, &palette)) { updateColorTreeFrom(&palette); } else { QMessageBox::critical(this, tr("Error"), tr("An unknown error occurred while loading the color scheme.")); } } void ConfigurationDialog::on_colorSavePushButton_clicked() { QString file = QFileDialog::getSaveFileName(this, tr("Save color scheme"), QApplication::applicationDirPath() + "/skins", tr("ini file (*.ini)") + ";;" + tr("All files (*)"), nullptr #if defined(Q_OS_LINUX) || (defined(Q_OS_BSD4) && !defined(Q_OS_DARWIN)) , QFileDialog::DontUseNativeDialog #endif ); if (file.isNull()) return; if (!file.endsWith(".ini")) file += ".ini"; // For linux ColorPalette palette; setPaletteFromColorTree(&palette); if (!io::savePalette(file, &palette)) QMessageBox::critical(this, tr("Error"), tr("Failed to save the color scheme.")); } void ConfigurationDialog::updateColorTreeFrom(const ColorPalette* const palette) { QTreeWidgetItem* ptnColors = ui->colorsTreeWidget->topLevelItem(0); ptnColors->child(0)->setData(1, Qt::BackgroundRole, palette->ptnDefTextColor); ptnColors->child(1)->setData(1, Qt::BackgroundRole, palette->ptnDefStepColor); ptnColors->child(2)->setData(1, Qt::BackgroundRole, palette->ptnHl1StepColor); ptnColors->child(3)->setData(1, Qt::BackgroundRole, palette->ptnHl2StepColor); ptnColors->child(4)->setData(1, Qt::BackgroundRole, palette->ptnCurTextColor); ptnColors->child(5)->setData(1, Qt::BackgroundRole, palette->ptnCurStepColor); ptnColors->child(6)->setData(1, Qt::BackgroundRole, palette->ptnCurEditStepColor); ptnColors->child(7)->setData(1, Qt::BackgroundRole, palette->ptnCurCellColor); ptnColors->child(8)->setData(1, Qt::BackgroundRole, palette->ptnPlayStepColor); ptnColors->child(9)->setData(1, Qt::BackgroundRole, palette->ptnSelCellColor); ptnColors->child(10)->setData(1, Qt::BackgroundRole, palette->ptnHovCellColor); ptnColors->child(11)->setData(1, Qt::BackgroundRole, palette->ptnDefStepNumColor); ptnColors->child(12)->setData(1, Qt::BackgroundRole, palette->ptnHl1StepNumColor); ptnColors->child(13)->setData(1, Qt::BackgroundRole, palette->ptnHl2StepNumColor); ptnColors->child(14)->setData(1, Qt::BackgroundRole, palette->ptnNoteColor); ptnColors->child(15)->setData(1, Qt::BackgroundRole, palette->ptnInstColor); ptnColors->child(16)->setData(1, Qt::BackgroundRole, palette->ptnVolColor); ptnColors->child(17)->setData(1, Qt::BackgroundRole, palette->ptnEffColor); ptnColors->child(18)->setData(1, Qt::BackgroundRole, palette->ptnErrorColor); ptnColors->child(19)->setData(1, Qt::BackgroundRole, palette->ptnHeaderTextColor); ptnColors->child(20)->setData(1, Qt::BackgroundRole, palette->ptnHeaderRowColor); ptnColors->child(21)->setData(1, Qt::BackgroundRole, palette->ptnMaskColor); ptnColors->child(22)->setData(1, Qt::BackgroundRole, palette->ptnBorderColor); ptnColors->child(23)->setData(1, Qt::BackgroundRole, palette->ptnHeaderBorderColor); ptnColors->child(24)->setData(1, Qt::BackgroundRole, palette->ptnMuteColor); ptnColors->child(25)->setData(1, Qt::BackgroundRole, palette->ptnUnmuteColor); ptnColors->child(26)->setData(1, Qt::BackgroundRole, palette->ptnBackColor); ptnColors->child(27)->setData(1, Qt::BackgroundRole, palette->ptnMarkerColor); ptnColors->child(28)->setData(1, Qt::BackgroundRole, palette->ptnUnfocusedShadowColor); QTreeWidgetItem* odrColors = ui->colorsTreeWidget->topLevelItem(1); odrColors->child(0)->setData(1, Qt::BackgroundRole, palette->odrDefTextColor); odrColors->child(1)->setData(1, Qt::BackgroundRole, palette->odrDefRowColor); odrColors->child(2)->setData(1, Qt::BackgroundRole, palette->odrCurTextColor); odrColors->child(3)->setData(1, Qt::BackgroundRole, palette->odrCurRowColor); odrColors->child(4)->setData(1, Qt::BackgroundRole, palette->odrCurEditRowColor); odrColors->child(5)->setData(1, Qt::BackgroundRole, palette->odrCurCellColor); odrColors->child(6)->setData(1, Qt::BackgroundRole, palette->odrPlayRowColor); odrColors->child(7)->setData(1, Qt::BackgroundRole, palette->odrSelCellColor); odrColors->child(8)->setData(1, Qt::BackgroundRole, palette->odrHovCellColor); odrColors->child(9)->setData(1, Qt::BackgroundRole, palette->odrRowNumColor); odrColors->child(10)->setData(1, Qt::BackgroundRole, palette->odrHeaderTextColor); odrColors->child(11)->setData(1, Qt::BackgroundRole, palette->odrHeaderRowColor); odrColors->child(12)->setData(1, Qt::BackgroundRole, palette->odrBorderColor); odrColors->child(13)->setData(1, Qt::BackgroundRole, palette->odrHeaderBorderColor); odrColors->child(14)->setData(1, Qt::BackgroundRole, palette->odrBackColor); odrColors->child(15)->setData(1, Qt::BackgroundRole, palette->odrUnfocusedShadowColor); QTreeWidgetItem* ilistColors = ui->colorsTreeWidget->topLevelItem(2); ilistColors->child(0)->setData(1, Qt::BackgroundRole, palette->ilistTextColor); ilistColors->child(1)->setData(1, Qt::BackgroundRole, palette->ilistBackColor); ilistColors->child(2)->setData(1, Qt::BackgroundRole, palette->ilistSelBackColor); ilistColors->child(3)->setData(1, Qt::BackgroundRole, palette->ilistHovBackColor); ilistColors->child(4)->setData(1, Qt::BackgroundRole, palette->ilistHovSelBackColor); QTreeWidgetItem* wavColors = ui->colorsTreeWidget->topLevelItem(3); wavColors->child(0)->setData(1, Qt::BackgroundRole, palette->wavBackColor); wavColors->child(1)->setData(1, Qt::BackgroundRole, palette->wavDrawColor); } void ConfigurationDialog::setPaletteFromColorTree(ColorPalette* const palette) { QTreeWidgetItem* ptnColors = ui->colorsTreeWidget->topLevelItem(0); palette->ptnDefTextColor = ptnColors->child(0)->data(1, Qt::BackgroundRole).value(); palette->ptnDefStepColor = ptnColors->child(1)->data(1, Qt::BackgroundRole).value(); palette->ptnHl1StepColor = ptnColors->child(2)->data(1, Qt::BackgroundRole).value(); palette->ptnHl2StepColor = ptnColors->child(3)->data(1, Qt::BackgroundRole).value(); palette->ptnCurTextColor = ptnColors->child(4)->data(1, Qt::BackgroundRole).value(); palette->ptnCurStepColor = ptnColors->child(5)->data(1, Qt::BackgroundRole).value(); palette->ptnCurEditStepColor = ptnColors->child(6)->data(1, Qt::BackgroundRole).value(); palette->ptnCurCellColor = ptnColors->child(7)->data(1, Qt::BackgroundRole).value(); palette->ptnPlayStepColor = ptnColors->child(8)->data(1, Qt::BackgroundRole).value(); palette->ptnSelCellColor = ptnColors->child(9)->data(1, Qt::BackgroundRole).value(); palette->ptnHovCellColor = ptnColors->child(10)->data(1, Qt::BackgroundRole).value(); palette->ptnDefStepNumColor = ptnColors->child(11)->data(1, Qt::BackgroundRole).value(); palette->ptnHl1StepNumColor = ptnColors->child(12)->data(1, Qt::BackgroundRole).value(); palette->ptnHl2StepNumColor = ptnColors->child(13)->data(1, Qt::BackgroundRole).value(); palette->ptnNoteColor = ptnColors->child(14)->data(1, Qt::BackgroundRole).value(); palette->ptnInstColor = ptnColors->child(15)->data(1, Qt::BackgroundRole).value(); palette->ptnVolColor = ptnColors->child(16)->data(1, Qt::BackgroundRole).value(); palette->ptnEffColor = ptnColors->child(17)->data(1, Qt::BackgroundRole).value(); palette->ptnErrorColor = ptnColors->child(18)->data(1, Qt::BackgroundRole).value(); palette->ptnHeaderTextColor = ptnColors->child(19)->data(1, Qt::BackgroundRole).value(); palette->ptnHeaderRowColor = ptnColors->child(20)->data(1, Qt::BackgroundRole).value(); palette->ptnMaskColor = ptnColors->child(21)->data(1, Qt::BackgroundRole).value(); palette->ptnBorderColor = ptnColors->child(22)->data(1, Qt::BackgroundRole).value(); palette->ptnHeaderBorderColor = ptnColors->child(23)->data(1, Qt::BackgroundRole).value(); palette->ptnMuteColor = ptnColors->child(24)->data(1, Qt::BackgroundRole).value(); palette->ptnUnmuteColor = ptnColors->child(25)->data(1, Qt::BackgroundRole).value(); palette->ptnBackColor = ptnColors->child(26)->data(1, Qt::BackgroundRole).value(); palette->ptnMarkerColor = ptnColors->child(27)->data(1, Qt::BackgroundRole).value(); palette->ptnUnfocusedShadowColor = ptnColors->child(28)->data(1, Qt::BackgroundRole).value(); QTreeWidgetItem* odrColors = ui->colorsTreeWidget->topLevelItem(1); palette->odrDefTextColor = odrColors->child(0)->data(1, Qt::BackgroundRole).value(); palette->odrDefRowColor = odrColors->child(1)->data(1, Qt::BackgroundRole).value(); palette->odrCurTextColor = odrColors->child(2)->data(1, Qt::BackgroundRole).value(); palette->odrCurRowColor = odrColors->child(3)->data(1, Qt::BackgroundRole).value(); palette->odrCurEditRowColor = odrColors->child(4)->data(1, Qt::BackgroundRole).value(); palette->odrCurCellColor = odrColors->child(5)->data(1, Qt::BackgroundRole).value(); palette->odrPlayRowColor = odrColors->child(6)->data(1, Qt::BackgroundRole).value(); palette->odrSelCellColor = odrColors->child(7)->data(1, Qt::BackgroundRole).value(); palette->odrHovCellColor = odrColors->child(8)->data(1, Qt::BackgroundRole).value(); palette->odrRowNumColor = odrColors->child(9)->data(1, Qt::BackgroundRole).value(); palette->odrHeaderTextColor = odrColors->child(10)->data(1, Qt::BackgroundRole).value(); palette->odrHeaderRowColor = odrColors->child(11)->data(1, Qt::BackgroundRole).value(); palette->odrBorderColor = odrColors->child(12)->data(1, Qt::BackgroundRole).value(); palette->odrHeaderBorderColor = odrColors->child(13)->data(1, Qt::BackgroundRole).value(); palette->odrBackColor = odrColors->child(14)->data(1, Qt::BackgroundRole).value(); palette->odrUnfocusedShadowColor = odrColors->child(15)->data(1, Qt::BackgroundRole).value(); QTreeWidgetItem* ilistColors = ui->colorsTreeWidget->topLevelItem(2); palette->ilistTextColor = ilistColors->child(0)->data(1, Qt::BackgroundRole).value(); palette->ilistBackColor = ilistColors->child(1)->data(1, Qt::BackgroundRole).value(); palette->ilistSelBackColor = ilistColors->child(2)->data(1, Qt::BackgroundRole).value(); palette->ilistHovBackColor = ilistColors->child(3)->data(1, Qt::BackgroundRole).value(); palette->ilistHovSelBackColor = ilistColors->child(4)->data(1, Qt::BackgroundRole).value(); QTreeWidgetItem* wavColors = ui->colorsTreeWidget->topLevelItem(3); palette->wavBackColor = wavColors->child(0)->data(1, Qt::BackgroundRole).value(); palette->wavDrawColor = wavColors->child(1)->data(1, Qt::BackgroundRole).value(); } BambooTracker-0.6.5/BambooTracker/gui/configuration_dialog.hpp000066400000000000000000000071061476276175200244600ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef CONFIGURATION_DIALOG_HPP #define CONFIGURATION_DIALOG_HPP #include #include #include #include #include #include #include #include "configuration.hpp" #include "color_palette.hpp" #include "enum_hash.hpp" namespace Ui { class ConfigurationDialog; } class AudioStream; class ConfigurationDialog : public QDialog { Q_OBJECT public: ConfigurationDialog(std::weak_ptr config, std::weak_ptr palette, std::weak_ptr stream, QWidget *parent = nullptr); ~ConfigurationDialog() override; signals: void applyPressed(); private slots: void on_ConfigurationDialog_accepted(); private: Ui::ConfigurationDialog *ui; std::weak_ptr config_; std::weak_ptr refPalette_; std::weak_ptr stream_; std::unordered_map customLayoutKeysMap_; /***** General *****/ private slots: void on_generalSettingsListWidget_itemSelectionChanged(); void on_noteNameComboBox_currentIndexChanged(int index); /***** Mixer *****/ private slots: void on_mixerResetPushButton_clicked(); /***** Sound *****/ private slots: void on_audioApiComboBox_currentIndexChanged(const QString &arg1); void on_midiApiComboBox_currentIndexChanged(const QString &arg1); void onMidiApiChanged(const QString &arg1, bool hasInitialized = true); /***** Formats *****/ private: std::vector fmEnvelopeTexts_; private slots: void on_addEnvelopeSetPushButton_clicked(); void on_removeEnvelopeSetpushButton_clicked(); void on_editEnvelopeSetPushButton_clicked(); void on_envelopeSetNameLineEdit_textChanged(const QString &arg1); void on_envelopeTypeListWidget_currentRowChanged(int currentRow); void updateEnvelopeSetUi(); /***** Keys *****/ private slots: void on_keyboardTypeComboBox_currentIndexChanged(int); void on_customLayoutResetButton_clicked(); private: std::unordered_map shortcutsMap_; void addShortcutItem(QString action, std::string shortcut); std::string getShortcutString(int row) const; void updateNoteNames(); /***** Appearance *****/ private slots: void on_colorEditPushButton_clicked(); void on_colorLoadPushButton_clicked(); void on_colorSavePushButton_clicked(); private: void updateColorTreeFrom(const ColorPalette* const palette); void setPaletteFromColorTree(ColorPalette* const palette); }; #endif // CONFIGURATION_DIALOG_HPP BambooTracker-0.6.5/BambooTracker/gui/configuration_dialog.ui000066400000000000000000002555661476276175200243250ustar00rootroot00000000000000 ConfigurationDialog 0 0 550 483 Configuration 0 General Wave view Frame rate fps 20 144 30 Qt::Vertical 20 40 Edit settings Page jump length 1 128 4 General settings Wrap cursor Checked Wrap across orders Checked Show row numbers in hex Checked Preview previous/next orders Checked Backup modules Checked Don't select on double click Unchecked Reverse FM volume order Checked Move cursor right Unchecked Retrieve channel state Unchecked Enable translation Checked Show FM detune as signed Unchecked Fill 00 to effect value Checked Move cursor with horizontal scroll bar Checked Overwrite unused and unedited properties Unchecked Write only used samples Unchecked Reflect instrument number change Unchecked Fix jamming volume Checked Mute hidden tracks Checked Restore track visibility Unchecked Overflow paste mode Unchecked Qt::NoFocus true Description: Note names Notation system Sound Sample rate Resampler Buffer length 1 500 Qt::Horizontal 1ms Qt::AlignCenter Qt::Vertical 20 40 MIDI input true false Device API Audio output Real chip interface Device API Emulation core 0 0 Zero-wait write Mixer Part 0 0 QFrame::StyledPanel QFrame::Raised 0 0 QFrame::StyledPanel QFrame::Raised The level setting for each part is valid when the mixer in the module properties is not checked. true Reset QFrame::StyledPanel QFrame::Raised Appearance Font and size Pattern editor header Pattern editor rows Order list header Order list rows Colors Qt::Vertical 20 40 Edit QAbstractItemView::NoEditTriggers QAbstractItemView::SelectItems Item Color Pattern editor Default step text Default step background Highlighted step 1 background Highlighted step 2 background Current step text Current step background Current editing step background Current cell background Current playing step background Selection background Hovered cell background Default step number Highlighted step 1 number Highlighted step 2 number Note text Instrument text Volume text Effect text Error text Header text Header background Mask Border Header border Mute Unmute Background Marker Unfocused shadow Order list Default row text Default row background Current row text Current row background Current editing row background Current cell background Current playing row background Selection background Hovered cell background Row number Header text Header background Border Header border Background Unfocused shadow Instrument list Default text Background Selected background Hovered background Selected hovered background Oscilloscope Background Foreground Save Load Formats FM envelope text Add false Edit false Remove Qt::Vertical 20 40 false Keys Shortcuts false true 2 Action Keys 0 0 16777215 216 Note entry layout Custom 0 Custom QWERTY QWERTZ AZERTY 0 0 90 16777215 Reset 0 0 16777215 120 QLabel { border: 1px solid rgb(0, 0, 0); } QTabWidget::North QTabWidget::Rounded 0 false false Low 0 2 0 0 16 0 16777215 50 0 0 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); QFrame::NoFrame QFrame::Plain C Qt::AlignBottom|Qt::AlignHCenter false 16 0 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); QFrame::NoFrame C# false Qt::AlignBottom|Qt::AlignHCenter 16 0 true false background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); QFrame::NoFrame D Qt::AlignBottom|Qt::AlignHCenter 16 0 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); D# Qt::AlignBottom|Qt::AlignHCenter 16 0 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); E Qt::AlignBottom|Qt::AlignHCenter 16 0 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); F Qt::AlignBottom|Qt::AlignHCenter 16 0 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); F# Qt::AlignBottom|Qt::AlignHCenter 16 0 false true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); G Qt::AlignBottom|Qt::AlignHCenter 16 0 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); G# Qt::AlignBottom|Qt::AlignHCenter 16 0 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); A Qt::AlignBottom|Qt::AlignHCenter 16 0 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); A# Qt::AlignBottom|Qt::AlignHCenter 16 0 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); B Qt::AlignBottom|Qt::AlignHCenter Qt::Horizontal QSizePolicy::Fixed 5 20 16 0 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); C Qt::AlignBottom|Qt::AlignHCenter 16 0 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); C# Qt::AlignBottom|Qt::AlignHCenter 16 0 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); D Qt::AlignBottom|Qt::AlignHCenter 16 0 16 0 16 0 16 0 16 0 16 0 16 0 16 0 16 0 16 0 16 0 16 0 16 0 16 0 16 0 High 0 2 16 0 16 0 16 0 16 0 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); C# Qt::AlignBottom|Qt::AlignHCenter 16 0 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); F Qt::AlignBottom|Qt::AlignHCenter 16 0 16 0 16 0 16 0 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); D Qt::AlignBottom|Qt::AlignHCenter 16 0 16 0 16 0 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); G# Qt::AlignBottom|Qt::AlignHCenter 16 0 16 0 16 0 16 0 16 0 true false background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); QFrame::NoFrame D Qt::AlignBottom|Qt::AlignHCenter 16 0 false true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); G Qt::AlignBottom|Qt::AlignHCenter 16 0 16 0 16 0 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); D# Qt::AlignBottom|Qt::AlignHCenter 16 0 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); A Qt::AlignBottom|Qt::AlignHCenter 16 0 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); C Qt::AlignBottom|Qt::AlignHCenter 16 0 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); E Qt::AlignBottom|Qt::AlignHCenter 16 0 16 0 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); QFrame::NoFrame C# false Qt::AlignBottom|Qt::AlignHCenter 16 0 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); A# Qt::AlignBottom|Qt::AlignHCenter 16 0 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); B Qt::AlignBottom|Qt::AlignHCenter 0 0 16 0 16777215 50 0 0 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); QFrame::NoFrame QFrame::Plain C Qt::AlignBottom|Qt::AlignHCenter false 16 0 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); F# Qt::AlignBottom|Qt::AlignHCenter Qt::Horizontal QSizePolicy::Fixed 5 20 Qt::Horizontal QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok LabeledVerticalSlider QFrame
gui/labeled_vertical_slider.hpp
1
FontInfoWidget QWidget
gui/font_info_widget.hpp
1
tabWidget generalSettingsListWidget pageJumpLengthSpinBox waveViewRateSpinBox noteNameComboBox emulatorComboBox audioApiComboBox audioDeviceComboBox realChipComboBox midiInputGroupBox midiApiComboBox midiInputDeviceComboBox sampleRateComboBox bufferLengthHorizontalSlider mixerResetPushButton colorsTreeWidget colorLoadPushButton colorSavePushButton colorEditPushButton envelopeTypeListWidget envelopeSetNameLineEdit addEnvelopeSetPushButton removeEnvelopeSetpushButton editEnvelopeSetPushButton shortcutsTreeWidget keyboardTypeComboBox customLayoutResetButton lowHighKeysTabWidget lowCEdit lowCSEdit lowDEdit lowDSEdit lowEEdit lowFEdit lowFSEdit lowGEdit lowGSEdit lowAEdit lowASEdit lowBEdit lowHighCEdit lowHighCSEdit lowHighDEdit highCEdit highCSEdit highDEdit highDSEdit highEEdit highFEdit highFSEdit highGEdit highGSEdit highAEdit highASEdit highBEdit highHighCEdit highHighCSEdit highHighDEdit buttonBox accepted() ConfigurationDialog accept() 266 395 157 274 buttonBox rejected() ConfigurationDialog reject() 334 395 286 274 envelopeTypeListWidget itemDoubleClicked(QListWidgetItem*) editEnvelopeSetPushButton click() 185 189 412 262
BambooTracker-0.6.5/BambooTracker/gui/configuration_handler.cpp000066400000000000000000000730121476276175200246300ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "configuration_handler.hpp" #include #include #include #include #include #include "configuration.hpp" #include "jamming.hpp" #include "enum_hash.hpp" #include "gui/gui_utils.hpp" #include "utils.hpp" namespace io { namespace { // config path (*nix): ~/.config//.ini const QString APPLICATION = "BambooTracker"; const std::unordered_map SHORTCUTS_NAME_MAP = { { Configuration::ShortcutAction::KeyOff, "keyOff" }, { Configuration::ShortcutAction::KeyCut, "keyCut" }, { Configuration::ShortcutAction::OctaveUp, "octaveUp" }, { Configuration::ShortcutAction::OctaveDown, "octaveDown" }, { Configuration::ShortcutAction::EchoBuffer, "echoBuffer" }, { Configuration::ShortcutAction::PlayAndStop, "playAndStop" }, { Configuration::ShortcutAction::Play, "play" }, { Configuration::ShortcutAction::PlayFromStart, "playFromStart" }, { Configuration::ShortcutAction::PlayPattern, "playPattern" }, { Configuration::ShortcutAction::PlayFromCursor, "playFromCursor" }, { Configuration::ShortcutAction::PlayFromMarker, "playFromMarker" }, { Configuration::ShortcutAction::PlayStep, "playStep" }, { Configuration::ShortcutAction::Stop, "stop" }, { Configuration::ShortcutAction::FocusOnPattern, "ocusOnPattern" }, { Configuration::ShortcutAction::FocusOnOrder, "focusOnOrder" }, { Configuration::ShortcutAction::FocusOnInstrument, "focusOnInstrument" }, { Configuration::ShortcutAction::ToggleEditJam, "toggleEditJam" }, { Configuration::ShortcutAction::SetMarker, "setMarker" }, { Configuration::ShortcutAction::PasteMix, "pasteMix" }, { Configuration::ShortcutAction::PasteOverwrite, "pasteOverwrite" }, { Configuration::ShortcutAction::PasteInsert, "pasteInsert" }, { Configuration::ShortcutAction::SelectAll, "selectAll" }, { Configuration::ShortcutAction::Deselect, "deselect" }, { Configuration::ShortcutAction::SelectRow, "selectRow" }, { Configuration::ShortcutAction::SelectColumn, "selectColumn" }, { Configuration::ShortcutAction::SelectPattern, "selectPattern" }, { Configuration::ShortcutAction::SelectOrder, "selectOrder" }, { Configuration::ShortcutAction::GoToStep, "goToStep" }, { Configuration::ShortcutAction::ToggleTrack, "toggleTrack" }, { Configuration::ShortcutAction::SoloTrack, "soloTrack" }, { Configuration::ShortcutAction::Interpolate, "interpolate" }, { Configuration::ShortcutAction::Reverse, "reverse" }, { Configuration::ShortcutAction::GoToPrevOrder, "goToPrevOrder" }, { Configuration::ShortcutAction::GoToNextOrder, "goToNextOrder" }, { Configuration::ShortcutAction::ToggleBookmark, "toggleBookmark" }, { Configuration::ShortcutAction::PrevBookmark, "prevBookmark" }, { Configuration::ShortcutAction::NextBookmark, "nextBookmark" }, { Configuration::ShortcutAction::DecreaseNote, "decreaseNote" }, { Configuration::ShortcutAction::IncreaseNote, "increaseNote" }, { Configuration::ShortcutAction::DecreaseOctave, "decreaseOctave" }, { Configuration::ShortcutAction::IncreaseOctave, "increaseOctave" }, { Configuration::ShortcutAction::PrevInstrument, "prevInstrument" }, { Configuration::ShortcutAction::NextInstrument, "nextInstrument" }, { Configuration::ShortcutAction::MaskInstrument, "maskInstrument" }, { Configuration::ShortcutAction::MaskVolume, "maskVolume" }, { Configuration::ShortcutAction::EditInstrument, "editInstrument" }, { Configuration::ShortcutAction::FollowMode, "followMode" }, { Configuration::ShortcutAction::DuplicateOrder, "duplicateOrder" }, { Configuration::ShortcutAction::ClonePatterns, "clonePatterns" }, { Configuration::ShortcutAction::CloneOrder, "cloneOrder" }, { Configuration::ShortcutAction::ReplaceInstrument, "replaceInstrument" }, { Configuration::ShortcutAction::ExpandPattern, "expandPattern" }, { Configuration::ShortcutAction::ShrinkPattern, "shrinkPattern" }, { Configuration::ShortcutAction::FineDecreaseValues, "fineDecreaseValues" }, { Configuration::ShortcutAction::FineIncreaseValues, "fineIncreaseValues" }, { Configuration::ShortcutAction::CoarseDecreaseValues, "coarseDecreaseValues" }, { Configuration::ShortcutAction::CoarseIncreaseValuse, "coarseIncreaseValuse" }, { Configuration::ShortcutAction::ExpandEffect, "expandEffect" }, { Configuration::ShortcutAction::ShrinkEffect, "shrinkEffect" }, { Configuration::ShortcutAction::PrevHighlighted, "prevHighlightedStep" }, { Configuration::ShortcutAction::NextHighlighted, "nextHighlightedStep" }, { Configuration::ShortcutAction::IncreasePatternSize, "incPtnSize" }, { Configuration::ShortcutAction::DecreasePatternSize, "decPtnSize" }, { Configuration::ShortcutAction::IncreaseEditStep, "incEditStep" }, { Configuration::ShortcutAction::DecreaseEditStep, "decEditStep" }, { Configuration::ShortcutAction::DisplayEffectList, "dispEffectList" }, { Configuration::ShortcutAction::PreviousSong, "prevSong" }, { Configuration::ShortcutAction::NextSong, "nextSong" }, { Configuration::ShortcutAction::JamVolumeUp, "jamVolumeUp" }, { Configuration::ShortcutAction::JamVolumeDown, "jamVolumeDown" } }; const std::unordered_map JAM_KEY_NAME_MAP = { {JamKey::LowC, "lowC"}, {JamKey::LowCS, "lowCS"}, {JamKey::LowD, "lowD"}, {JamKey::LowDS, "lowDS"}, {JamKey::LowE, "lowE"}, {JamKey::LowF, "lowF"}, {JamKey::LowFS, "lowFS"}, {JamKey::LowG, "lowG"}, {JamKey::LowGS, "lowGS"}, {JamKey::LowA, "lowA"}, {JamKey::LowAS, "lowAS"}, {JamKey::LowB, "lowB"}, {JamKey::LowC2, "lowHighC"}, {JamKey::LowCS2, "lowHighCS"}, {JamKey::LowD2, "lowHighD"}, {JamKey::HighC, "highC"}, {JamKey::HighCS, "highCS"}, {JamKey::HighD, "highD"}, {JamKey::HighDS, "highDS"}, {JamKey::HighE, "highE"}, {JamKey::HighF, "highF"}, {JamKey::HighFS, "highFS"}, {JamKey::HighG, "highG"}, {JamKey::HighGS, "highGS"}, {JamKey::HighA, "highA"}, {JamKey::HighAS, "highAS"}, {JamKey::HighB, "highB"}, {JamKey::HighC2, "highHighC"}, {JamKey::HighCS2, "highHighCS"}, {JamKey::HighD2, "highHighD"} }; } bool saveConfiguration(std::weak_ptr config) { try { QSettings settings(QSettings::IniFormat, QSettings::UserScope, io::ORGANIZATION_NAME, APPLICATION); std::shared_ptr configLocked = config.lock(); // Internal // settings.beginGroup("Internal"); settings.setValue("mainWindowWidth", configLocked->getMainWindowWidth()); settings.setValue("mainWindowHeight", configLocked->getMainWindowHeight()); settings.setValue("mainWindowMaximized", configLocked->getMainWindowMaximized()); settings.setValue("mainWindowX", configLocked->getMainWindowX()); settings.setValue("mainWindowY", configLocked->getMainWindowY()); settings.setValue("mainWindowVerticalSplit", configLocked->getMainWindowVerticalSplit()); settings.setValue("instrumentFMWindowWidth", configLocked->getInstrumentFMWindowWidth()); settings.setValue("instrumentFMWindowHeight", configLocked->getInstrumentFMWindowHeight()); settings.setValue("instrumentSSGWindowWidth", configLocked->getInstrumentSSGWindowWidth()); settings.setValue("instrumentSSGWindowHeight", configLocked->getInstrumentSSGWindowHeight()); settings.setValue("instrumentADPCMWindowWidth", configLocked->getInstrumentADPCMWindowWidth()); settings.setValue("instrumentADPCMWindowHeight", configLocked->getInstrumentADPCMWindowHeight()); settings.setValue("instrumentDrumkitWindowWidth", configLocked->getInstrumentDrumkitWindowWidth()); settings.setValue("instrumentDrumkitWindowHeight", configLocked->getInstrumentDrumkitWindowHeight()); settings.setValue("instrumentDrumkitWindowHorizontalSplit", configLocked->getInstrumentDrumkitWindowHorizontalSplit()); settings.setValue("followMode", configLocked->getFollowMode()); settings.setValue("workingDirectory", QString::fromStdString(configLocked->getWorkingDirectory())); settings.setValue("instrumentOpenFormat", configLocked->getInstrumentOpenFormat()); settings.setValue("bankOpenFormat", configLocked->getBankOpenFormat()); settings.setValue("instrumentMask", configLocked->getInstrumentMask()); settings.setValue("volumeMask", configLocked->getVolumeMask()); settings.setValue("visibleToolbar", configLocked->getVisibleToolbar()); settings.setValue("visibleStatusBar", configLocked->getVisibleStatusBar()); settings.setValue("visibleWaveView", configLocked->getVisibleWaveView()); settings.setValue("pasteMode", static_cast(configLocked->getPasteMode())); auto& mainTbConfig = configLocked->getMainToolbarConfiguration(); settings.setValue("mainToolbarPosition", static_cast(mainTbConfig.getPosition())); settings.setValue("mainToolbarNumber", mainTbConfig.getNumber()); settings.setValue("hasBreakBeforeMainToolbar", mainTbConfig.hasBreakBefore()); settings.setValue("mainToolbarX", mainTbConfig.getX()); settings.setValue("mainToolbarY", mainTbConfig.getY()); auto& subTbConfig = configLocked->getSubToolbarConfiguration(); settings.setValue("subToolbarPosition", static_cast(subTbConfig.getPosition())); settings.setValue("subToolbarNumber", subTbConfig.getNumber()); settings.setValue("hasBreakBeforesubToolbar", subTbConfig.hasBreakBefore()); settings.setValue("subToolbarX", subTbConfig.getX()); settings.setValue("subToolbarY", subTbConfig.getY()); settings.endGroup(); // General // // General settings settings.beginGroup("General"); settings.setValue("warpCursor", configLocked->getWarpCursor()); settings.setValue("warpAcrossOrders", configLocked->getWarpAcrossOrders()); settings.setValue("showRowNumberInHex", configLocked->getShowRowNumberInHex()); settings.setValue("showPreviousNextOrders", configLocked->getShowPreviousNextOrders()); settings.setValue("backupModule", configLocked->getBackupModules()); settings.setValue("dontSelectOnDoubleClick", configLocked->getDontSelectOnDoubleClick()); settings.setValue("reverseFMVolumeOrder", configLocked->getReverseFMVolumeOrder()); settings.setValue("moveCursorToRight", configLocked->getMoveCursorToRight()); settings.setValue("retrieveChannelState", configLocked->getRetrieveChannelState()); settings.setValue("enableTranslation", configLocked->getEnableTranslation()); settings.setValue("showFMDetuneAsSigned", configLocked->getShowFMDetuneAsSigned()); settings.setValue("fill00ToEffectValue", configLocked->getFill00ToEffectValue()); settings.setValue("moveCursorByHScroll", configLocked->getMoveCursorByHorizontalScroll()); settings.setValue("overwriteUnusedUnedited", configLocked->getOverwriteUnusedUneditedPropety()); settings.setValue("writeOnlyUsedSamples", configLocked->getWriteOnlyUsedSamples()); settings.setValue("reflectInstNumChange", configLocked->getReflectInstrumentNumberChange()); settings.setValue("fixJammingVolume", configLocked->getFixJammingVolume()); settings.setValue("muteHiddenTracks", configLocked->getMuteHiddenTracks()); settings.setValue("restoreTrackVisibility", configLocked->getRestoreTrackVisibility()); settings.setValue("overflowPaste", configLocked->getOverflowPaste()); settings.endGroup(); // Edit settings settings.beginGroup("Editing"); settings.setValue("pageJumpLength", static_cast(configLocked->getPageJumpLength())); settings.setValue("editableStep", static_cast(configLocked->getEditableStep())); settings.setValue("keyRepetition", configLocked->getKeyRepetition()); settings.endGroup(); // Wave view settings.beginGroup("WaveView"); settings.setValue("frameRate", configLocked->getWaveViewFrameRate()); settings.endGroup(); // Note names settings.beginGroup("NoteNames"); settings.setValue("notationSystem", static_cast(configLocked->getNotationSystem())); settings.endGroup(); // Keys settings.beginGroup("Keys"); for (const auto& pair : configLocked->getShortcuts()) { settings.setValue("shortcut_" + SHORTCUTS_NAME_MAP.at(pair.first), gui_utils::utf8ToQString(pair.second)); } settings.setValue("noteEntryLayout", static_cast(configLocked->getNoteEntryLayout())); for (const auto& pair : configLocked->getCustomLayoutKeys()) { settings.setValue("customLayout_" + JAM_KEY_NAME_MAP.at(pair.second), gui_utils::utf8ToQString(pair.first)); } settings.endGroup(); // Sound // settings.beginGroup("Sound"); settings.setValue("soundAPI", gui_utils::utf8ToQString(configLocked->getSoundAPI())); settings.setValue("soundDevice", gui_utils::utf8ToQString(configLocked->getSoundDevice())); settings.setValue("realChipInterface", static_cast(configLocked->getRealChipInterface())); settings.setValue("emulator", configLocked->getEmulator()); settings.setValue("sampleRate", static_cast(configLocked->getSampleRate())); settings.setValue("bufferLength", static_cast(configLocked->getBufferLength())); settings.setValue("resamplerType", static_cast(configLocked->getResamplerType())); settings.setValue("immediateWriteModeEnabled", configLocked->getImmediateWriteModeEnabled()); settings.endGroup(); // Midi // settings.beginGroup("Midi"); settings.setValue("midiEnabled", configLocked->getMidiEnabled()); settings.setValue("midiAPI", QString::fromStdString(configLocked->getMidiAPI())); settings.setValue("inputPort", QString::fromStdString(configLocked->getMidiInputPort())); settings.endGroup(); // Mixer // settings.beginGroup("Mixer"); settings.setValue("mixerVolumeMaster", configLocked->getMixerVolumeMaster()); settings.setValue("mixerVolumeFM", configLocked->getMixerVolumeFM()); settings.setValue("mixerVolumeSSG", configLocked->getMixerVolumeSSG()); settings.endGroup(); // Input // settings.beginGroup("Input"); settings.beginWriteArray("fmEnvelopeTextMap"); int n = 0; for (const FMEnvelopeText& texts : config.lock()->getFMEnvelopeTexts()) { settings.setArrayIndex(n++); settings.setValue("type", gui_utils::utf8ToQString(texts.name)); QStringList typeList; std::transform(texts.texts.begin(), texts.texts.end(), std::back_inserter(typeList), [](FMEnvelopeTextType type) { return QString::number(static_cast(type)); }); settings.setValue("order", typeList.join(",")); } settings.endArray(); settings.endGroup(); settings.beginGroup("Appearance"); settings.setValue("patternEditorHeaderFont", QString::fromStdString(configLocked->getPatternEditorHeaderFont())); settings.setValue("patternEditorRowsFont", QString::fromStdString(configLocked->getPatternEditorRowsFont())); settings.setValue("orderListHeaderFont", QString::fromStdString(configLocked->getOrderListHeaderFont())); settings.setValue("orderListRowsFont", QString::fromStdString(configLocked->getOrderListRowsFont())); settings.endGroup(); return true; } catch (...) { return false; } } bool loadConfiguration(std::weak_ptr config) { try { QSettings settings(QSettings::IniFormat, QSettings::UserScope, io::ORGANIZATION_NAME, APPLICATION); if (!QFile(settings.fileName()).exists()) return false; std::shared_ptr configLocked = config.lock(); // Internal // settings.beginGroup("Internal"); configLocked->setMainWindowWidth(settings.value("mainWindowWidth", configLocked->getMainWindowWidth()).toInt()); configLocked->setMainWindowHeight(settings.value("mainWindowHeight", configLocked->getMainWindowHeight()).toInt()); configLocked->setMainWindowMaximized(settings.value("mainWindowMaximized", configLocked->getMainWindowMaximized()).toBool()); configLocked->setMainWindowX(settings.value("mainWindowX", configLocked->getMainWindowX()).toInt()); configLocked->setMainWindowY(settings.value("mainWindowY", configLocked->getMainWindowY()).toInt()); configLocked->setMainWindowVerticalSplit(settings.value("mainWindowVerticalSplit", configLocked->getMainWindowVerticalSplit()).toInt()); configLocked->setInstrumentFMWindowWidth(settings.value("instrumentFMWindowWidth", configLocked->getInstrumentFMWindowWidth()).toInt()); configLocked->setInstrumentFMWindowHeight(settings.value("instrumentFMWindowHeight", configLocked->getInstrumentFMWindowHeight()).toInt()); configLocked->setInstrumentSSGWindowWidth(settings.value("instrumentSSGWindowWidth", configLocked->getInstrumentSSGWindowWidth()).toInt()); configLocked->setInstrumentSSGWindowHeight(settings.value("instrumentSSGWindowHeight", configLocked->getInstrumentSSGWindowHeight()).toInt()); configLocked->setInstrumentADPCMWindowWidth(settings.value("instrumentADPCMWindowWidth", configLocked->getInstrumentADPCMWindowWidth()).toInt()); configLocked->setInstrumentADPCMWindowHeight(settings.value("instrumentADPCMWindowHeight", configLocked->getInstrumentADPCMWindowHeight()).toInt()); configLocked->setInstrumentDrumkitWindowWidth(settings.value("instrumentDrumkitWindowWidth", configLocked->getInstrumentDrumkitWindowWidth()).toInt()); configLocked->setInstrumentDrumkitWindowHorizontalSplit(settings.value("instrumentDrumkitWindowHorizontalSplit", configLocked->getInstrumentDrumkitWindowHorizontalSplit()).toInt()); configLocked->setInstrumentDrumkitWindowHeight(settings.value("instrumentDrumkitWindowHeight", configLocked->getInstrumentDrumkitWindowHeight()).toInt()); configLocked->setFollowMode(settings.value("followMode", configLocked->getFollowMode()).toBool()); configLocked->setWorkingDirectory(settings.value("workingDirectory", QString::fromStdString(configLocked->getWorkingDirectory())).toString().toStdString()); configLocked->setInstrumentOpenFormat(settings.value("instrumentOpenFormat", configLocked->getInstrumentOpenFormat()).toInt()); configLocked->setBankOpenFormat(settings.value("bankOpenFormat", configLocked->getBankOpenFormat()).toInt()); configLocked->setInstrumentMask(settings.value("instrumentMask", configLocked->getInstrumentMask()).toBool()); configLocked->setVolumeMask(settings.value("volumeMask", configLocked->getVolumeMask()).toBool()); configLocked->setVisibleToolbar(settings.value("visibleToolbar", configLocked->getVisibleToolbar()).toBool()); configLocked->setVisibleStatusBar(settings.value("visibleStatusBar", configLocked->getVisibleStatusBar()).toBool()); configLocked->setVisibleWaveView(settings.value("visibleWaveView", configLocked->getVisibleWaveView()).toBool()); configLocked->setPasteMode(static_cast(settings.value("pasteMode", static_cast(configLocked->getPasteMode())).toInt())); auto& mainTbConfig = configLocked->getMainToolbarConfiguration(); mainTbConfig.setPosition(static_cast(settings.value("mainToolbarPosition", static_cast(mainTbConfig.getPosition())).toInt())); mainTbConfig.setNumber(settings.value("mainToolbarNumber", mainTbConfig.getNumber()).toInt()); mainTbConfig.setBreakBefore(settings.value("hasBreakBeforeMainToolbar", mainTbConfig.hasBreakBefore()).toBool()); mainTbConfig.setX(settings.value("mainToolbarX", mainTbConfig.getX()).toInt()); mainTbConfig.setY(settings.value("mainToolbarY", mainTbConfig.getY()).toInt()); auto& subTbConfig = configLocked->getSubToolbarConfiguration(); subTbConfig.setPosition(static_cast(settings.value("subToolbarPosition", static_cast(subTbConfig.getPosition())).toInt())); subTbConfig.setNumber(settings.value("subToolbarNumber", subTbConfig.getNumber()).toInt()); subTbConfig.setBreakBefore(settings.value("hasBreakBeforesubToolbar", subTbConfig.hasBreakBefore()).toBool()); subTbConfig.setX(settings.value("subToolbarX", subTbConfig.getX()).toInt()); subTbConfig.setY(settings.value("subToolbarY", subTbConfig.getY()).toInt()); settings.endGroup(); // General // // General settings settings.beginGroup("General"); configLocked->setWarpCursor(settings.value("warpCursor", configLocked->getWarpCursor()).toBool()); configLocked->setWarpAcrossOrders(settings.value("warpAcrossOrders", configLocked->getWarpAcrossOrders()).toBool()); configLocked->setShowRowNumberInHex(settings.value("showRowNumberInHex", configLocked->getShowRowNumberInHex()).toBool()); configLocked->setShowPreviousNextOrders(settings.value("showPreviousNextOrders", configLocked->getShowPreviousNextOrders()).toBool()); configLocked->setBackupModules(settings.value("backupModule", configLocked->getBackupModules()).toBool()); configLocked->setDontSelectOnDoubleClick(settings.value("dontSelectOnDoubleClick", configLocked->getDontSelectOnDoubleClick()).toBool()); configLocked->setReverseFMVolumeOrder(settings.value("reverseFMVolumeOrder", configLocked->getReverseFMVolumeOrder()).toBool()); configLocked->setMoveCursorToRight(settings.value("moveCursorToRight", configLocked->getMoveCursorToRight()).toBool()); configLocked->setRetrieveChannelState(settings.value("retrieveChannelState", configLocked->getRetrieveChannelState()).toBool()); configLocked->setEnableTranslation(settings.value("enableTranslation", configLocked->getEnableTranslation()).toBool()); configLocked->setShowFMDetuneAsSigned(settings.value("showFMDetuneAsSigned", configLocked->getShowFMDetuneAsSigned()).toBool()); configLocked->setFill00ToEffectValue(settings.value("fill00ToEffectValue", configLocked->getFill00ToEffectValue()).toBool()); configLocked->setMoveCursorByHorizontalScroll(settings.value("moveCursorByHScroll", configLocked->getMoveCursorByHorizontalScroll()).toBool()); configLocked->setOverwriteUnusedUneditedPropety(settings.value("overwriteUnusedUnedited", configLocked->getOverwriteUnusedUneditedPropety()).toBool()); configLocked->setWriteOnlyUsedSamples(settings.value("writeOnlyUsedSamples", configLocked->getWriteOnlyUsedSamples()).toBool()); configLocked->setReflectInstrumentNumberChange(settings.value("reflectInstNumChange", configLocked->getReflectInstrumentNumberChange()).toBool()); configLocked->setFixJammingVolume(settings.value("fixJammingVolume", configLocked->getFixJammingVolume()).toBool()); configLocked->setMuteHiddenTracks(settings.value("muteHiddenTracks", configLocked->getMuteHiddenTracks()).toBool()); configLocked->setRestoreTrackVisibility(settings.value("restoreTrackVisibility", configLocked->getRestoreTrackVisibility()).toBool()); configLocked->setOverflowPaste(settings.value("overflowPaste", configLocked->getOverflowPaste()).toBool()); if (settings.contains("autosetInstrument")) { // For compatibility before v0.4.0 configLocked->setInstrumentMask(!settings.value("autosetInstrument").toBool()); settings.remove("autosetInstrument"); } if (settings.contains("showWaveVisual")) { // For compatibility before v0.4.2 configLocked->setVisibleWaveView(settings.value("showWaveVisual").toBool()); settings.remove("showWaveVisual"); } settings.endGroup(); // Edit settings settings.beginGroup("Editing"); QVariant pageJumpLengthWorkaround; pageJumpLengthWorkaround.setValue(configLocked->getPageJumpLength()); configLocked->setPageJumpLength(static_cast(settings.value("pageJumpLength", pageJumpLengthWorkaround).toInt())); QVariant editableStepWorkaround; editableStepWorkaround.setValue(configLocked->getEditableStep()); configLocked->setEditableStep(static_cast(settings.value("editableStep", editableStepWorkaround).toInt())); configLocked->setKeyRepetition(settings.value("keyRepetition", configLocked->getKeyRepetition()).toBool()); settings.endGroup(); // Wave view settings.beginGroup("WaveView"); configLocked->setWaveViewFrameRate(settings.value("frameRate", configLocked->getWaveViewFrameRate()).toInt()); settings.endGroup(); // Note names settings.beginGroup("NoteNames"); configLocked->setNotationSystem( static_cast(settings.value("notationSystem", static_cast(configLocked->getNotationSystem())).toInt())); settings.endGroup(); // Keys settings.beginGroup("Keys"); std::unordered_map shortcuts; for (const auto& pair : SHORTCUTS_NAME_MAP) { std::string def = configLocked->getShortcuts().at(pair.first); shortcuts[pair.first] = settings.value("shortcut_" + pair.second, gui_utils::utf8ToQString(def)).toString().toUtf8().toStdString(); } configLocked->setShortcuts(shortcuts); configLocked->setNoteEntryLayout(static_cast( settings.value("noteEntryLayout", static_cast(configLocked->getNoteEntryLayout())).toInt())); std::unordered_map customLayoutNewKeys; for (const auto& pair : JAM_KEY_NAME_MAP) { JamKey currentlyWantedJamKey = pair.first; customLayoutNewKeys[ settings.value("customLayout_" + pair.second, QString::fromStdString((*utils::findIf(configLocked->mappingLayouts.at(Configuration::KeyboardLayout::QWERTY), [currentlyWantedJamKey](const std::pair& t) -> bool { return (t.second) == currentlyWantedJamKey;}) ).first)).toString().toUtf8().toStdString()] = currentlyWantedJamKey; } configLocked->setCustomLayoutKeys(customLayoutNewKeys); settings.endGroup(); // Sound // settings.beginGroup("Sound"); configLocked->setSoundAPI(settings.value("soundAPI", QString::fromStdString(configLocked->getSoundAPI())).toString().toUtf8().toStdString()); configLocked->setSoundDevice(settings.value("soundDevice", QString::fromStdString(configLocked->getSoundDevice())).toString().toUtf8().toStdString()); configLocked->setRealChipInterface(static_cast( settings.value("realChipInterface", static_cast(configLocked->getRealChipInterface())).toInt())); configLocked->setEmulator(settings.value("emulator", configLocked->getEmulator()).toInt()); QVariant sampleRateWorkaround; sampleRateWorkaround.setValue(configLocked->getSampleRate()); configLocked->setSampleRate(static_cast(settings.value("sampleRate", sampleRateWorkaround).toInt())); QVariant bufferLengthWorkaround; bufferLengthWorkaround.setValue(configLocked->getBufferLength()); configLocked->setBufferLength(static_cast(settings.value("bufferLength", bufferLengthWorkaround).toInt())); configLocked->setResamplerType(static_cast( settings.value("resamplerType", static_cast(configLocked->getResamplerType())).toInt())); configLocked->setImmediateWriteModeEnabled(settings.value("immediateWriteModeEnabled", configLocked->getImmediateWriteModeEnabled()).toBool()); settings.endGroup(); // Midi // settings.beginGroup("Midi"); configLocked->setMidiEnabled(settings.value("midiEnabled", configLocked->getMidiEnabled()).toBool()); configLocked->setMidiAPI(settings.value("midiAPI", QString::fromStdString(configLocked->getMidiAPI())).toString().toStdString()); configLocked->setMidiInputPort(settings.value("inputPort", QString::fromStdString(configLocked->getMidiInputPort())).toString().toStdString()); settings.endGroup(); // Mixer // settings.beginGroup("Mixer"); configLocked->setMixerVolumeMaster(settings.value("mixerVolumeMaster", configLocked->getMixerVolumeMaster()).toInt()); configLocked->setMixerVolumeFM(settings.value("mixerVolumeFM", configLocked->getMixerVolumeFM()).toDouble()); configLocked->setMixerVolumeSSG(settings.value("mixerVolumeSSG", configLocked->getMixerVolumeSSG()).toDouble()); settings.endGroup(); // Input // settings.beginGroup("Input"); int size = settings.beginReadArray("fmEnvelopeTextMap"); std::vector fmEnvelopeTexts; for (int i = 0; i < size; ++i) { settings.setArrayIndex(i); std::string type = settings.value("type").toString().toUtf8().toStdString(); std::vector data; const QStringList list = settings.value("order").toString().split(","); for (const QString& d : list) { data.push_back(static_cast(d.toInt())); } fmEnvelopeTexts.push_back({ type, data }); } if (!fmEnvelopeTexts.empty()) config.lock()->setFMEnvelopeTexts(fmEnvelopeTexts); settings.endArray(); settings.endGroup(); // Appearance settings.beginGroup("Appearance"); configLocked->setPatternEditorHeaderFont(settings.value("patternEditorHeaderFont", QString::fromStdString(configLocked->getPatternEditorHeaderFont())).toString().toStdString()); configLocked->setPatternEditorRowsFont(settings.value("patternEditorRowsFont", QString::fromStdString(configLocked->getPatternEditorRowsFont())).toString().toStdString()); configLocked->setOrderListHeaderFont(settings.value("orderListHeaderFont", QString::fromStdString(configLocked->getOrderListHeaderFont())).toString().toStdString()); configLocked->setOrderListRowsFont(settings.value("orderListRowsFont", QString::fromStdString(configLocked->getOrderListRowsFont())).toString().toStdString()); settings.endGroup(); return true; } catch (...) { return false; } } } BambooTracker-0.6.5/BambooTracker/gui/configuration_handler.hpp000066400000000000000000000026041476276175200246340ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef CONFIGURATION_HANDLER_HPP #define CONFIGURATION_HANDLER_HPP #include class Configuration; namespace io { bool saveConfiguration(std::weak_ptr config); bool loadConfiguration(std::weak_ptr config); } #endif // CONFIGURATION_HANDLER_HPP BambooTracker-0.6.5/BambooTracker/gui/dpi.hpp000066400000000000000000000021651476276175200210460ustar00rootroot00000000000000#pragma once #include #include namespace Dpi { inline QPixmap scaledQPixmap(QSize size, int ratio) { QPixmap out(size * ratio); out.setDevicePixelRatio(ratio); return out; } inline QPixmap scaledQPixmap(int width, int height, int ratio) { QPixmap out(width * ratio, height * ratio); out.setDevicePixelRatio(ratio); return out; } inline int iRatio(QWidget const& w) { // devicePixelRatio is int on Qt 5 and qreal on Qt 6. // This shouldn't result in *too many* behavior differences though, // since devicePixelRatioF is an integer on Qt 5, // unless KDE sets QT_SCREEN_SCALE_FACTORS (we can't workaround) // or we set DPI scaling to PassThrough (we don't). auto ratio = w.devicePixelRatio(); // Fails on Linux KDE due to https://bugreports.qt.io/browse/QTBUG-95930. // Q_ASSERT((int) ratio == ratio); return (int) ratio; } inline QRect scaleRect(QRect rect, int ratio) { // QRect is insane. QRectF is sane. // QRect(QPoint(0, 0), QSize(10, 10)).right() == 9 // QRectF(QPointF(0., 0.), QSizeF(10., 10.)).right() == 10. return QRect(rect.topLeft() * ratio, rect.size() * ratio); } } // namespace BambooTracker-0.6.5/BambooTracker/gui/drop_detect_list_widget.cpp000066400000000000000000000036231476276175200251570ustar00rootroot00000000000000/* * Copyright (C) 2020-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "drop_detect_list_widget.hpp" #include #include #include #include DropDetectListWidget::DropDetectListWidget(QWidget* parent) : QListWidget(parent) {} void DropDetectListWidget::dropEvent(QDropEvent* event) { QListWidget::dropEvent(event); if (event->source() == this) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QPoint pos = event->position().toPoint(); #else QPoint pos = event->pos(); #endif QListWidgetItem* tgt = itemAt(pos); if (tgt == nullptr) return; int tgtIdx = indexAt(pos).row(); int drpIdx; { // Only 1 item stored QByteArray&& ary = event->mimeData()->data("application/x-qabstractitemmodeldatalist"); QDataStream(&ary, QIODevice::ReadOnly) >> drpIdx; } emit itemDroppedAtItemIndex(drpIdx, tgtIdx); } } BambooTracker-0.6.5/BambooTracker/gui/drop_detect_list_widget.hpp000066400000000000000000000027461476276175200251710ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef DROP_DETECT_LIST_WIDGET_HPP #define DROP_DETECT_LIST_WIDGET_HPP #include class DropDetectListWidget : public QListWidget { Q_OBJECT public: explicit DropDetectListWidget(QWidget* parent = nullptr); signals: void itemDroppedAtItemIndex(int dropped, int target); protected: void dropEvent(QDropEvent* event) override; }; #endif // DROP_DETECT_LIST_WIDGET_HPP BambooTracker-0.6.5/BambooTracker/gui/effect_description.cpp000066400000000000000000000156321476276175200241270ustar00rootroot00000000000000/* * Copyright (C) 2019-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "effect_description.hpp" #include #include #include "enum_hash.hpp" namespace effect_desc { namespace { struct EffectDetail { const QString format; const char* desc; }; const std::unordered_map DETAILS = { { EffectType::Arpeggio, { "00xy", QT_TRANSLATE_NOOP("EffectDescription", "Arpeggio, x: 2nd note (0-F), y: 3rd note (0-F)") } }, { EffectType::PortamentoUp, { "01xx", QT_TRANSLATE_NOOP("EffectDescription", "Portamento up, xx: depth (00-FF)") } }, { EffectType::PortamentoDown, { "02xx", QT_TRANSLATE_NOOP("EffectDescription", "Portamento down, xx: depth (00-FF)") } }, { EffectType::TonePortamento, { "03xx", QT_TRANSLATE_NOOP("EffectDescription", "Tone portamento, xx: depth (00-FF)") } }, { EffectType::Vibrato, { "04xy", QT_TRANSLATE_NOOP("EffectDescription", "Vibrato, x: period (0-F), y: depth (0-F)") } }, { EffectType::Tremolo, { "07xx", QT_TRANSLATE_NOOP("EffectDescription", "Tremolo, x: period (0-F), y: depth (0-F)") } }, { EffectType::Pan, { "08xx", QT_TRANSLATE_NOOP("EffectDescription", "Pan, xx: 00 = no sound, 01 = right, 02 = left, 03 = center") } }, { EffectType::VolumeSlide, { "0Axy", QT_TRANSLATE_NOOP("EffectDescription", "Volume slide, x: up (0-F), y: down (0-F)") } }, { EffectType::PositionJump, { "0Bxx", QT_TRANSLATE_NOOP("EffectDescription", "Jump to beginning of order xx") } }, { EffectType::SongEnd, { "0Cxx", QT_TRANSLATE_NOOP("EffectDescription", "End of song") } }, { EffectType::PatternBreak, { "0Dxx", QT_TRANSLATE_NOOP("EffectDescription", "Jump to step xx of next order") } }, { EffectType::SpeedTempoChange, { "0Fxx", QT_TRANSLATE_NOOP("EffectDescription", "Change speed (xx: 00-1F), change tempo (xx: 20-FF)") } }, { EffectType::NoteDelay, { "0Gxx", QT_TRANSLATE_NOOP("EffectDescription", "Note delay, xx: count (00-FF)") } }, { EffectType::AutoEnvelope, { "0Hxy", QT_TRANSLATE_NOOP("EffectDescription", "Auto envelope, x: shift amount (0-F), y: shape (0-F)") } }, { EffectType::HardEnvHighPeriod, { "0Ixx", QT_TRANSLATE_NOOP("EffectDescription", "Hardware envelope period 1, xx: high byte (00-FF)") } }, { EffectType::HardEnvLowPeriod, { "0Jxx", QT_TRANSLATE_NOOP("EffectDescription", "Hardware envelope period 2, xx: low byte (00-FF)") } }, { EffectType::Retrigger, { "0Kxy", QT_TRANSLATE_NOOP("EffectDescription", "Retrigger, x: volume slide (0-7: up, 8-F: down), y: tick (1-F)") } }, { EffectType::Groove, { "0Oxx", QT_TRANSLATE_NOOP("EffectDescription", "Set groove xx") } }, { EffectType::Detune, { "0Pxx", QT_TRANSLATE_NOOP("EffectDescription", "Detune, xx: pitch (00-FF)") } }, { EffectType::NoteSlideUp, { "0Qxy", QT_TRANSLATE_NOOP("EffectDescription", "Note slide up, x: count (0-F), y: semitone (0-F)") } }, { EffectType::NoteSlideDown, { "0Rxy", QT_TRANSLATE_NOOP("EffectDescription", "Note slide down, x: count (0-F), y: semitone (0-F)") } }, { EffectType::NoteRelease, { "0Sxx", QT_TRANSLATE_NOOP("EffectDescription", "Note release, xx: count (00-FF)") } }, { EffectType::TransposeDelay, { "0Txy", QT_TRANSLATE_NOOP("EffectDescription", "Transpose delay, x: count (0-7: up, 8-F: down), y: semitone (0-F)") } }, { EffectType::ToneNoiseMix, { "0Vxx", QT_TRANSLATE_NOOP("EffectDescription", "Tone/Noise mix, xx: 00 = no sound, 01 = tone, 02 = noise, 03 = tone & noise") } }, { EffectType::MasterVolume, { "0Vxx", QT_TRANSLATE_NOOP("EffectDescription", "Master volume, xx: volume (00-3F)") } }, { EffectType::NoisePitch, { "0Wxx", QT_TRANSLATE_NOOP("EffectDescription", "Noise pitch, xx: pitch (00-1F)") } }, { EffectType::RegisterAddress0, { "0Xxx", QT_TRANSLATE_NOOP("EffectDescription", "Register address bank 0, xx: address (00-6B)") } }, { EffectType::RegisterAddress1, { "0Yxx", QT_TRANSLATE_NOOP("EffectDescription", "Register address bank 1, xx: address (00-6B)") } }, { EffectType::RegisterValue, { "0Zxx", QT_TRANSLATE_NOOP("EffectDescription", "Register value set, xx: value (00-FF)") } }, { EffectType::ARControl, { "Axyy", QT_TRANSLATE_NOOP("EffectDescription", "AR control, x: operator (1-4), yy: attack rate (00-1F)") } }, { EffectType::Brightness, { "B0xx", QT_TRANSLATE_NOOP("EffectDescription", "Brightness, xx: relative value (01-FF)") } }, { EffectType::DRControl, { "Dxyy", QT_TRANSLATE_NOOP("EffectDescription", "DR control, x: operator (1-4), yy: decay rate (00-1F)") } }, { EffectType::XVolumeSlide, { "EAxy", QT_TRANSLATE_NOOP("EffectDescription", "Extended volume slide, x: up (0-F), y: down (0-F)") } }, { EffectType::NoteCut, { "ESxx", QT_TRANSLATE_NOOP("EffectDescription", "Note cut, xx: count (00-FF)") } }, { EffectType::FBControl, { "FBxx", QT_TRANSLATE_NOOP("EffectDescription", "FB control, xx: feedback value (00-07)") } }, { EffectType::FineDetune, { "FPxx", QT_TRANSLATE_NOOP("EffectDescription", "Fine detune, xx: pitch (00-FF)") } }, { EffectType::MLControl, { "MLxy", QT_TRANSLATE_NOOP("EffectDescription", "ML control, x: operator (1-4), y: multiple (0-F)") } }, { EffectType::VolumeDelay, { "Mxyy", QT_TRANSLATE_NOOP("EffectDescription", "Volume delay, x: count (0-F), yy: volume (00-FF)") } }, { EffectType::RRControl, { "RRxy", QT_TRANSLATE_NOOP("EffectDescription", "RR control, x: operator (1-4), y: release rate (0-F)") } }, { EffectType::TLControl, { "Txyy", QT_TRANSLATE_NOOP("EffectDescription", "TL control, x: operator (1-4), yy: total level (00-7F)") } }, { EffectType::NoEffect, { "", QT_TRANSLATE_NOOP("EffectDescription", "Invalid effect") } } }; } QString getEffectFormat(const EffectType type) { return DETAILS.at(type).format; } QString getEffectDescription(const EffectType type) { return QCoreApplication::translate("EffectDescription", DETAILS.at(type).desc); } QString getEffectFormatAndDetailString(const EffectType type) { if (type == EffectType::NoEffect) { return getEffectDescription(EffectType::NoEffect); } else { return getEffectFormat(type) + " - " + getEffectDescription(type); } } } BambooTracker-0.6.5/BambooTracker/gui/effect_description.hpp000066400000000000000000000026571476276175200241370ustar00rootroot00000000000000/* * Copyright (C) 2019-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef EFFECT_DESCRIPTION_HPP #define EFFECT_DESCRIPTION_HPP #include #include "effect.hpp" namespace effect_desc { QString getEffectFormat(const EffectType type); QString getEffectDescription(const EffectType type); QString getEffectFormatAndDetailString(const EffectType type); } #endif // EFFECT_DESCRIPTION_HPP BambooTracker-0.6.5/BambooTracker/gui/effect_list_dialog.cpp000066400000000000000000000134541476276175200240760ustar00rootroot00000000000000/* * Copyright (C) 2019-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "effect_list_dialog.hpp" #include "ui_effect_list_dialog.h" #include #include #include "gui/effect_description.hpp" EffectListDialog::EffectListDialog(QWidget *parent) : QDialog(parent), ui(new Ui::EffectListDialog) { ui->setupUi(this); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); ui->tableWidget->setColumnWidth(0, 50); ui->tableWidget->setColumnWidth(1, 100); addRow(EffectType::Arpeggio, { SoundSource::FM, SoundSource::SSG, SoundSource::ADPCM }); addRow(EffectType::PortamentoUp, { SoundSource::FM, SoundSource::SSG, SoundSource::ADPCM }); addRow(EffectType::PortamentoDown, { SoundSource::FM, SoundSource::SSG, SoundSource::ADPCM }); addRow(EffectType::TonePortamento, { SoundSource::FM, SoundSource::SSG, SoundSource::ADPCM }); addRow(EffectType::Vibrato, { SoundSource::FM, SoundSource::SSG, SoundSource::ADPCM }); addRow(EffectType::Tremolo, { SoundSource::FM, SoundSource::SSG, SoundSource::ADPCM }); addRow(EffectType::Pan, { SoundSource::FM, SoundSource::RHYTHM, SoundSource::ADPCM }); addRow(EffectType::VolumeSlide, { SoundSource::FM, SoundSource::SSG, SoundSource::ADPCM }); addRow(EffectType::PositionJump, { SoundSource::FM, SoundSource::SSG, SoundSource::RHYTHM, SoundSource::ADPCM }); addRow(EffectType::SongEnd, { SoundSource::FM, SoundSource::SSG, SoundSource::RHYTHM, SoundSource::ADPCM }); addRow(EffectType::PatternBreak, { SoundSource::FM, SoundSource::SSG, SoundSource::RHYTHM, SoundSource::ADPCM }); addRow(EffectType::SpeedTempoChange, { SoundSource::FM, SoundSource::SSG, SoundSource::RHYTHM, SoundSource::ADPCM }); addRow(EffectType::NoteDelay, { SoundSource::FM, SoundSource::SSG, SoundSource::RHYTHM, SoundSource::ADPCM }); addRow(EffectType::AutoEnvelope, { SoundSource::SSG }); addRow(EffectType::HardEnvHighPeriod, { SoundSource::SSG }); addRow(EffectType::HardEnvLowPeriod, { SoundSource::SSG }); addRow(EffectType::Retrigger, { SoundSource::FM, SoundSource::SSG, SoundSource::RHYTHM, SoundSource::ADPCM }); addRow(EffectType::Groove, { SoundSource::FM, SoundSource::SSG, SoundSource::RHYTHM, SoundSource::ADPCM }); addRow(EffectType::Detune, { SoundSource::FM, SoundSource::SSG, SoundSource::ADPCM }); addRow(EffectType::NoteSlideUp, { SoundSource::FM, SoundSource::SSG, SoundSource::ADPCM }); addRow(EffectType::NoteSlideDown, { SoundSource::FM, SoundSource::SSG, SoundSource::ADPCM }); addRow(EffectType::NoteRelease, { SoundSource::FM, SoundSource::SSG, SoundSource::RHYTHM, SoundSource::ADPCM }); addRow(EffectType::TransposeDelay, { SoundSource::FM, SoundSource::SSG, SoundSource::ADPCM }); addRow(EffectType::ToneNoiseMix, { SoundSource::SSG }); addRow(EffectType::MasterVolume, { SoundSource::RHYTHM }); addRow(EffectType::NoisePitch, { SoundSource::SSG }); addRow(EffectType::RegisterAddress0, { SoundSource::FM, SoundSource::SSG, SoundSource::RHYTHM, SoundSource::ADPCM }); addRow(EffectType::RegisterAddress1, { SoundSource::FM, SoundSource::SSG, SoundSource::RHYTHM, SoundSource::ADPCM }); addRow(EffectType::RegisterValue, { SoundSource::FM, SoundSource::SSG, SoundSource::RHYTHM, SoundSource::ADPCM }); addRow(EffectType::ARControl, { SoundSource::FM }); addRow(EffectType::Brightness, { SoundSource::FM }); addRow(EffectType::DRControl, { SoundSource::FM }); addRow(EffectType::XVolumeSlide, { SoundSource::FM, SoundSource::SSG, SoundSource::ADPCM }); addRow(EffectType::NoteCut, { SoundSource::FM, SoundSource::SSG, SoundSource::RHYTHM, SoundSource::ADPCM }); addRow(EffectType::FBControl, { SoundSource::FM }); addRow(EffectType::FineDetune, { SoundSource::FM, SoundSource::SSG, SoundSource::ADPCM }); addRow(EffectType::VolumeDelay, { SoundSource::FM, SoundSource::SSG, SoundSource::RHYTHM, SoundSource::ADPCM }); addRow(EffectType::MLControl, { SoundSource::FM }); addRow(EffectType::RRControl, { SoundSource::FM }); addRow(EffectType::TLControl, { SoundSource::FM }); } EffectListDialog::~EffectListDialog() { delete ui; } void EffectListDialog::addRow(EffectType effect, std::unordered_set types) { int row = ui->tableWidget->rowCount(); ui->tableWidget->insertRow(row); ui->tableWidget->setItem(row, 0, new QTableWidgetItem(effect_desc::getEffectFormat(effect))); ui->tableWidget->setRowHeight(row, ui->tableWidget->horizontalHeader()->height()); auto trackTypes = { SoundSource::FM, SoundSource::SSG, SoundSource::RHYTHM, SoundSource::ADPCM }; int col = 1; for (const auto trackType : trackTypes){ ui->tableWidget->setItem(row, col, new QTableWidgetItem(QString(types.count(trackType) ? "✓" : ""))); ui->tableWidget->resizeColumnToContents(col); col++; } ui->tableWidget->setItem(row, col, new QTableWidgetItem(effect_desc::getEffectDescription(effect))); } BambooTracker-0.6.5/BambooTracker/gui/effect_list_dialog.hpp000066400000000000000000000031111476276175200240700ustar00rootroot00000000000000/* * Copyright (C) 2019-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef EFFECT_LIST_DIALOG_HPP #define EFFECT_LIST_DIALOG_HPP #include #include #include "effect.hpp" #include "enum_hash.hpp" namespace Ui { class EffectListDialog; } class EffectListDialog : public QDialog { Q_OBJECT public: explicit EffectListDialog(QWidget *parent = nullptr); ~EffectListDialog(); private: Ui::EffectListDialog *ui; void addRow(EffectType effect, std::unordered_set types); }; #endif // EFFECT_LIST_DIALOG_HPP BambooTracker-0.6.5/BambooTracker/gui/effect_list_dialog.ui000066400000000000000000000047651476276175200237360ustar00rootroot00000000000000 EffectListDialog 0 0 530 400 Effect list QAbstractItemView::NoEditTriggers 27 true false Effect FM SSG Rhythm ADPCM Description Qt::Horizontal QDialogButtonBox::Close buttonBox accepted() EffectListDialog accept() 248 254 157 274 buttonBox rejected() EffectListDialog reject() 316 260 286 274 BambooTracker-0.6.5/BambooTracker/gui/event_guard.cpp000066400000000000000000000024701476276175200225670ustar00rootroot00000000000000/* * Copyright (C) 2018 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "event_guard.hpp" namespace Ui { EventGuard::EventGuard(bool& isIgnoreEvent) : isIgnoreEvent_(isIgnoreEvent) { isIgnoreEvent_ = true; } EventGuard::~EventGuard() { isIgnoreEvent_ = false; } } BambooTracker-0.6.5/BambooTracker/gui/event_guard.hpp000066400000000000000000000025001476276175200225660ustar00rootroot00000000000000/* * Copyright (C) 2018 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef EVENT_GUARD_HPP #define EVENT_GUARD_HPP namespace Ui { class EventGuard { public: explicit EventGuard(bool& isIgnoreEvent); ~EventGuard(); private: bool& isIgnoreEvent_; }; } #endif // EVENT_GUARD_HPP BambooTracker-0.6.5/BambooTracker/gui/file_history.cpp000066400000000000000000000053101476276175200227600ustar00rootroot00000000000000/* * Copyright (C) 2019-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "file_history.hpp" #include #include #include "utils.hpp" #include "gui/gui_utils.hpp" namespace { const int HISTORY_SIZE = 8; } void FileHistory::addFile(const QString& path) { auto it = utils::find(list_, path); if (it != list_.end()) list_.erase(it); list_.push_front(path); if (list_.size() > HISTORY_SIZE) list_.pop_back(); } void FileHistory::clearHistory() { list_.clear(); } QString FileHistory::at(size_t i) { return list_.at(i); } size_t FileHistory::size() const { return list_.size(); } bool FileHistory::empty() const { return list_.empty(); } namespace io { namespace { // config path (*nix): ~/.config//.ini const QString FILE = "FileHistory"; } bool saveFileHistory(std::weak_ptr history) { try { QSettings settings(QSettings::IniFormat, QSettings::UserScope, io::ORGANIZATION_NAME, FILE); settings.beginWriteArray("fileHistory"); int n = 0; for (size_t i = 0; i < history.lock()->size(); ++i) { settings.setArrayIndex(n++); settings.setValue("path", history.lock()->at(i)); } settings.endArray(); return true; } catch (...) { return false; } } bool loadFileHistory(std::weak_ptr history) { try { QSettings settings(QSettings::IniFormat, QSettings::UserScope, io::ORGANIZATION_NAME, FILE); int size = settings.beginReadArray("fileHistory"); history.lock()->clearHistory(); for (int i = size - 1; 0 <= i; --i) { settings.setArrayIndex(i); history.lock()->addFile(settings.value("path").toString()); } settings.endArray(); return true; } catch (...) { return false; } } } BambooTracker-0.6.5/BambooTracker/gui/file_history.hpp000066400000000000000000000030631476276175200227700ustar00rootroot00000000000000/* * Copyright (C) 2019-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef FILE_HISTORY_HPP #define FILE_HISTORY_HPP #include #include #include class FileHistory { public: void addFile(const QString& path); void clearHistory(); QString at(size_t i); size_t size() const; bool empty() const; private: std::deque list_; }; namespace io { bool saveFileHistory(std::weak_ptr history); bool loadFileHistory(std::weak_ptr history); } #endif // FILE_HISTORY_HPP BambooTracker-0.6.5/BambooTracker/gui/file_io_error_message_box.cpp000066400000000000000000000063621476276175200254630ustar00rootroot00000000000000/* * Copyright (C) 2020-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "file_io_error_message_box.hpp" #include #include namespace { const std::unordered_map FILE_NAMES = { { io::FileType::Mod, QT_TRANSLATE_NOOP("FileType", "module") }, { io::FileType::S98, QT_TRANSLATE_NOOP("FileType", "s98") }, { io::FileType::VGM, QT_TRANSLATE_NOOP("FileType", "vgm") }, { io::FileType::WAV, QT_TRANSLATE_NOOP("FileType", "wav") }, { io::FileType::Bank, QT_TRANSLATE_NOOP("FileType", "bank") }, { io::FileType::Inst, QT_TRANSLATE_NOOP("FileType", "instrument") } }; } FileIOErrorMessageBox::FileIOErrorMessageBox(const QString& file, bool isInput, io::FileType ftype, const QString desc, QWidget* parent) : parent_(parent), desc_(desc) { setText(file, isInput, ftype); } FileIOErrorMessageBox::FileIOErrorMessageBox(const QString& file, bool isInput, const io::FileIOError& e, QWidget* parent) : parent_(parent) { const io::FileIOError *err = &e; QString type = QCoreApplication::translate("FileType", FILE_NAMES.at(err->fileType())); if (dynamic_cast(err)) { desc_ = tr("Path does not exist."); } else if (dynamic_cast(err)) { desc_ = tr("Unsupported file format."); } else if (dynamic_cast(err)) { desc_ = tr("Could not load the %1 properly. " "Please make sure that you have the latest version of BambooTracker.").arg(file); } else if (auto ce = dynamic_cast(err)) { desc_ = tr("Could not load the %1. It may be corrupted. Stopped at %2.").arg(type).arg(ce->position()); } setText(file, isInput, e.fileType()); } void FileIOErrorMessageBox::setText(const QString& file, bool isInput, io::FileType ftype) { if (isInput) { text_ = tr("Failed to load %1.").arg(file); } else { switch (ftype) { case io::FileType::S98: case io::FileType::VGM: case io::FileType::WAV: text_ = tr("Failed to export to %1."); break; default: text_ = tr("Failed to save the %1."); break; } text_ = text_.arg(file); } } void FileIOErrorMessageBox::exec() { QMessageBox::critical(parent_, tr("Error"), text_ + "\n" + desc_); } BambooTracker-0.6.5/BambooTracker/gui/file_io_error_message_box.hpp000066400000000000000000000040051476276175200254600ustar00rootroot00000000000000/* * Copyright (C) 2020-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef FILE_IO_ERROR_MESSAGE_BOX_HPP #define FILE_IO_ERROR_MESSAGE_BOX_HPP #include #include #include #include "io/io_file_type.hpp" #include "io/file_io_error.hpp" #include "enum_hash.hpp" class FileIOErrorMessageBox : public QObject { Q_OBJECT public: FileIOErrorMessageBox(const QString& file, bool isInput, io::FileType ftype, const QString desc, QWidget* parent = nullptr); FileIOErrorMessageBox(const QString& file, bool isInput, const io::FileIOError& e, QWidget* parent = nullptr); void exec(); inline static void openError(const QString& file, bool isInput, io::FileType ftype, QWidget* parent = nullptr) { FileIOErrorMessageBox(file, isInput, ftype, tr("Could not open the file."), parent).exec(); } private: QWidget* parent_; QString text_, desc_; void setText(const QString& file, bool isInput, io::FileType ftype); }; #endif // FILE_IO_ERROR_MESSAGE_BOX_HPP BambooTracker-0.6.5/BambooTracker/gui/fm_envelope_set_edit_dialog.cpp000066400000000000000000000203641476276175200257640ustar00rootroot00000000000000/* * Copyright (C) 2019-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "fm_envelope_set_edit_dialog.hpp" #include "ui_fm_envelope_set_edit_dialog.h" #include #include "configuration.hpp" FMEnvelopeSetEditDialog::FMEnvelopeSetEditDialog(std::vector set, QWidget *parent) : QDialog(parent), ui(new Ui::FMEnvelopeSetEditDialog) { ui->setupUi(this); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); for (size_t i = 0; i < set.size(); ++i) { insertRow(static_cast(i), set.at(i)); } } FMEnvelopeSetEditDialog::~FMEnvelopeSetEditDialog() { delete ui; } std::vector FMEnvelopeSetEditDialog::getSet() { std::vector set; for (int i = 0; i < ui->treeWidget->topLevelItemCount(); ++i) { set.push_back(static_cast( qobject_cast(ui->treeWidget->itemWidget( ui->treeWidget->topLevelItem(i), 1))->currentData().toInt())); } return set; } void FMEnvelopeSetEditDialog::swapset(int aboveRow, int belowRow) { auto* tree = ui->treeWidget; QComboBox* belowBox = makeCombobox(); belowBox->setCurrentIndex(qobject_cast(tree->itemWidget(tree->topLevelItem(belowRow), 1))->currentIndex()); QTreeWidgetItem* below = tree->takeTopLevelItem(belowRow); if (tree->topLevelItemCount() > 2) { QComboBox* aboveBox = makeCombobox(); aboveBox->setCurrentIndex(qobject_cast(tree->itemWidget(tree->topLevelItem(aboveRow), 1))->currentIndex()); QTreeWidgetItem* above = tree->takeTopLevelItem(aboveRow); tree->insertTopLevelItem(aboveRow, below); tree->insertTopLevelItem(belowRow, above); tree->setItemWidget(below, 1, belowBox); tree->setItemWidget(above, 1, aboveBox); } else { tree->insertTopLevelItem(aboveRow, below); tree->setItemWidget(below, 1, belowBox); } if (!aboveRow || !belowRow) alignTreeOn1stItemChanged(); // Dummy set and delete to align for (int i = aboveRow; i < ui->treeWidget->topLevelItemCount(); ++i) { ui->treeWidget->topLevelItem(i)->setText(0, QString::number(i)); } } void FMEnvelopeSetEditDialog::insertRow(int row, FMEnvelopeTextType type) { if (row == -1) row = 0; auto item = new QTreeWidgetItem(); item->setText(0, QString::number(row)); QComboBox* box = makeCombobox(); for (int i = 0; i < box->count(); ++i) { if (static_cast(box->itemData(i).toInt()) == type) { box->setCurrentIndex(i); break; } } ui->treeWidget->insertTopLevelItem(row, item); ui->treeWidget->setItemWidget(item, 1, box); if (!row) alignTreeOn1stItemChanged(); // Dummy set and delete to align for (int i = row + 1; i < ui->treeWidget->topLevelItemCount(); ++i) { ui->treeWidget->topLevelItem(i)->setText(0, QString::number(i)); } } QComboBox* FMEnvelopeSetEditDialog::makeCombobox() { auto box = new QComboBox(); box->addItem(tr("Skip"), static_cast(FMEnvelopeTextType::Skip)); box->addItem("AL", static_cast(FMEnvelopeTextType::AL)); box->addItem("FB", static_cast(FMEnvelopeTextType::FB)); box->addItem("AR1", static_cast(FMEnvelopeTextType::AR1)); box->addItem("DR1", static_cast(FMEnvelopeTextType::DR1)); box->addItem("SR1", static_cast(FMEnvelopeTextType::SR1)); box->addItem("RR1", static_cast(FMEnvelopeTextType::RR1)); box->addItem("SL1", static_cast(FMEnvelopeTextType::SL1)); box->addItem("TL1", static_cast(FMEnvelopeTextType::TL1)); box->addItem("KS1", static_cast(FMEnvelopeTextType::KS1)); box->addItem("ML1", static_cast(FMEnvelopeTextType::ML1)); box->addItem("DT1", static_cast(FMEnvelopeTextType::DT1)); box->addItem("AR2", static_cast(FMEnvelopeTextType::AR2)); box->addItem("DR2", static_cast(FMEnvelopeTextType::DR2)); box->addItem("SR2", static_cast(FMEnvelopeTextType::SR2)); box->addItem("RR2", static_cast(FMEnvelopeTextType::RR2)); box->addItem("SL2", static_cast(FMEnvelopeTextType::SL2)); box->addItem("TL2", static_cast(FMEnvelopeTextType::TL2)); box->addItem("KS2", static_cast(FMEnvelopeTextType::KS2)); box->addItem("ML2", static_cast(FMEnvelopeTextType::ML2)); box->addItem("DT2", static_cast(FMEnvelopeTextType::DT2)); box->addItem("AR3", static_cast(FMEnvelopeTextType::AR3)); box->addItem("DR3", static_cast(FMEnvelopeTextType::DR3)); box->addItem("SR3", static_cast(FMEnvelopeTextType::SR3)); box->addItem("RR3", static_cast(FMEnvelopeTextType::RR3)); box->addItem("SL3", static_cast(FMEnvelopeTextType::SL3)); box->addItem("TL3", static_cast(FMEnvelopeTextType::TL3)); box->addItem("KS3", static_cast(FMEnvelopeTextType::KS3)); box->addItem("ML3", static_cast(FMEnvelopeTextType::ML3)); box->addItem("DT3", static_cast(FMEnvelopeTextType::DT3)); box->addItem("AR4", static_cast(FMEnvelopeTextType::AR4)); box->addItem("DR4", static_cast(FMEnvelopeTextType::DR4)); box->addItem("SR4", static_cast(FMEnvelopeTextType::SR4)); box->addItem("RR4", static_cast(FMEnvelopeTextType::RR4)); box->addItem("SL4", static_cast(FMEnvelopeTextType::SL4)); box->addItem("TL4", static_cast(FMEnvelopeTextType::TL4)); box->addItem("KS4", static_cast(FMEnvelopeTextType::KS4)); box->addItem("ML4", static_cast(FMEnvelopeTextType::ML4)); box->addItem("DT4", static_cast(FMEnvelopeTextType::DT4)); return box; } /// Dummy set and delete to align void FMEnvelopeSetEditDialog::alignTreeOn1stItemChanged() { auto tmp = new QTreeWidgetItem(); ui->treeWidget->insertTopLevelItem(1, tmp); delete ui->treeWidget->takeTopLevelItem(1); } void FMEnvelopeSetEditDialog::on_upToolButton_clicked() { int curRow = ui->treeWidget->currentIndex().row(); if (!curRow) return; swapset(curRow - 1, curRow); ui->treeWidget->setCurrentItem(ui->treeWidget->topLevelItem(curRow - 1)); } void FMEnvelopeSetEditDialog::on_downToolButton_clicked() { int curRow = ui->treeWidget->currentIndex().row(); if (curRow == ui->treeWidget->topLevelItemCount() - 1) return; swapset(curRow, curRow + 1); ui->treeWidget->setCurrentItem(ui->treeWidget->topLevelItem(curRow + 1)); } void FMEnvelopeSetEditDialog::on_addPushButton_clicked() { int row = ui->treeWidget->currentIndex().row(); insertRow(row, FMEnvelopeTextType::Skip); ui->treeWidget->setCurrentItem(ui->treeWidget->topLevelItem((row == -1) ? 0 : row)); ui->upToolButton->setEnabled(true); ui->downToolButton->setEnabled(true); ui->removePushButton->setEnabled(true); } void FMEnvelopeSetEditDialog::on_removePushButton_clicked() { int row = ui->treeWidget->currentIndex().row(); delete ui->treeWidget->takeTopLevelItem(row); for (int i = row; i < ui->treeWidget->topLevelItemCount(); ++i) { ui->treeWidget->topLevelItem(i)->setText(0, QString::number(i)); } if (!ui->treeWidget->topLevelItemCount()) { ui->upToolButton->setEnabled(false); ui->downToolButton->setEnabled(false); ui->removePushButton->setEnabled(false); } } void FMEnvelopeSetEditDialog::on_treeWidget_itemSelectionChanged() { if (ui->treeWidget->currentIndex().row() == -1) { ui->upToolButton->setEnabled(false); ui->downToolButton->setEnabled(false); ui->removePushButton->setEnabled(false); } else { ui->upToolButton->setEnabled(true); ui->downToolButton->setEnabled(true); ui->removePushButton->setEnabled(true); } } BambooTracker-0.6.5/BambooTracker/gui/fm_envelope_set_edit_dialog.hpp000066400000000000000000000037621476276175200257740ustar00rootroot00000000000000/* * Copyright (C) 2019-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef FM_ENVELOPE_SET_EDIT_DIALOG_HPP #define FM_ENVELOPE_SET_EDIT_DIALOG_HPP #include #include #include namespace Ui { class FMEnvelopeSetEditDialog; } enum class FMEnvelopeTextType; class FMEnvelopeSetEditDialog : public QDialog { Q_OBJECT public: explicit FMEnvelopeSetEditDialog(std::vector set, QWidget *parent = nullptr); ~FMEnvelopeSetEditDialog(); std::vector getSet(); private slots: void on_upToolButton_clicked(); void on_downToolButton_clicked(); void on_addPushButton_clicked(); void on_removePushButton_clicked(); void on_treeWidget_itemSelectionChanged(); private: Ui::FMEnvelopeSetEditDialog *ui; void swapset(int aboveRow, int belowRow); void insertRow(int row, FMEnvelopeTextType type); QComboBox* makeCombobox(); void alignTreeOn1stItemChanged(); }; #endif // FM_ENVELOPE_SET_EDIT_DIALOG_HPP BambooTracker-0.6.5/BambooTracker/gui/fm_envelope_set_edit_dialog.ui000066400000000000000000000107331476276175200256160ustar00rootroot00000000000000 FMEnvelopeSetEditDialog 0 0 400 300 Edit FM envelope set false 0 0 Qt::DownArrow Add 0 0 Set digit types in the order of appearance. false Remove false 0 0 Qt::UpArrow Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok Qt::Vertical 20 40 false true 2 Number Type treeWidget upToolButton downToolButton addPushButton removePushButton buttonBox accepted() FMEnvelopeSetEditDialog accept() 248 254 157 274 buttonBox rejected() FMEnvelopeSetEditDialog reject() 316 260 286 274 BambooTracker-0.6.5/BambooTracker/gui/font_info_widget.cpp000066400000000000000000000034241476276175200236100ustar00rootroot00000000000000/* * Copyright (C) 2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "font_info_widget.hpp" #include "ui_font_info_widget.h" #include FontInfoWidget::FontInfoWidget(QWidget *parent) : QWidget(parent), ui(new Ui::FontInfoWidget) { ui->setupUi(this); } FontInfoWidget::~FontInfoWidget() { delete ui; } void FontInfoWidget::setFontInfo(const QFont& font) { font_ = font; auto text = QString("%1, %2pt").arg(font.family()).arg(font.pointSize()); if (font.bold()) text += (", " + tr("Bold")); if (font.italic()) text += (", " + tr("Italic")); ui->label->setText(text); } void FontInfoWidget::on_pushButton_clicked() { bool ok = false; QFont font = QFontDialog::getFont(&ok, font_, this); if (ok) { setFontInfo(font); } } BambooTracker-0.6.5/BambooTracker/gui/font_info_widget.hpp000066400000000000000000000033431476276175200236150ustar00rootroot00000000000000/* * Copyright (C) 2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include namespace Ui { class FontInfoWidget; } /** * @brief Widget that has a label displaying a font descripton * and a button opening the font setting dialog. */ class FontInfoWidget : public QWidget { Q_OBJECT public: explicit FontInfoWidget(QWidget *parent = nullptr); ~FontInfoWidget(); /** * @brief Set font information. * @param font font. */ void setFontInfo(const QFont& font); /** * @brief get font. * @return Font. */ QFont getFontInfo() const { return font_; } private slots: void on_pushButton_clicked(); private: Ui::FontInfoWidget *ui; QFont font_; }; BambooTracker-0.6.5/BambooTracker/gui/font_info_widget.ui000066400000000000000000000026161476276175200234450ustar00rootroot00000000000000 FontInfoWidget 0 0 400 300 Form 0 0 0 0 0 0 QFrame::WinPanel QFrame::Sunken Custom BambooTracker-0.6.5/BambooTracker/gui/go_to_dialog.cpp000066400000000000000000000050511476276175200227100ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "go_to_dialog.hpp" #include "ui_go_to_dialog.h" #include #include "gui/gui_utils.hpp" GoToDialog::GoToDialog(std::weak_ptr bt, QWidget *parent) : QDialog(parent), ui(new Ui::GoToDialog), bt_(bt), song_(bt.lock()->getCurrentSongNumber()) { ui->setupUi(this); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); ui->orderSpinBox->setMaximum(bt.lock()->getOrderSize(song_) - 1); ui->orderSpinBox->setValue(bt.lock()->getCurrentOrderNumber()); ui->stepSpinBox->setMaximum(bt.lock()->getPatternSizeFromOrderNumber(song_, ui->orderSpinBox->value())); ui->stepSpinBox->setValue(bt.lock()->getCurrentStepNumber()); auto style = bt.lock()->getSongStyle(bt.lock()->getCurrentSongNumber()); for (auto& attrib : style.trackAttribs) { ui->trackComboBox->addItem( gui_utils::getTrackName(style.type, attrib.source, attrib.channelInSource), attrib.number); if (bt.lock()->getCurrentTrackAttribute().number == attrib.number) ui->trackComboBox->setCurrentIndex(ui->trackComboBox->count() - 1); } } GoToDialog::~GoToDialog() { delete ui; } int GoToDialog::getOrder() const { return ui->orderSpinBox->value(); } int GoToDialog::getStep() const { return ui->stepSpinBox->value(); } int GoToDialog::getTrack() const { return ui->trackComboBox->currentData().toInt(); } void GoToDialog::on_orderSpinBox_valueChanged(int arg1) { ui->stepSpinBox->setMaximum(bt_.lock()->getPatternSizeFromOrderNumber(song_, arg1)); } BambooTracker-0.6.5/BambooTracker/gui/go_to_dialog.hpp000066400000000000000000000032071476276175200227160ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef GO_TO_DIALOG_HPP #define GO_TO_DIALOG_HPP #include #include #include "bamboo_tracker.hpp" namespace Ui { class GoToDialog; } class GoToDialog : public QDialog { Q_OBJECT public: GoToDialog(std::weak_ptr bt, QWidget *parent = nullptr); ~GoToDialog() override; int getOrder() const; int getStep() const; int getTrack() const; private slots: void on_orderSpinBox_valueChanged(int arg1); private: Ui::GoToDialog *ui; std::weak_ptr bt_; int song_; }; #endif // GO_TO_DIALOG_HPP BambooTracker-0.6.5/BambooTracker/gui/go_to_dialog.ui000066400000000000000000000045471476276175200225540ustar00rootroot00000000000000 GoToDialog 0 0 300 78 Go To Order Step Track Qt::Horizontal Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() GoToDialog accept() 248 254 157 274 buttonBox rejected() GoToDialog reject() 316 260 286 274 BambooTracker-0.6.5/BambooTracker/gui/groove_settings_dialog.cpp000066400000000000000000000174241476276175200250310ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "groove_settings_dialog.hpp" #include "ui_groove_settings_dialog.h" #include #include #include #include #include #include #include "utils.hpp" GrooveSettingsDialog::GrooveSettingsDialog(QWidget *parent) : QDialog(parent), ui(new Ui::GrooveSettingsDialog) { ui->setupUi(this); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); ui->grooveListWidget->setSelectionMode(QAbstractItemView::SingleSelection); } GrooveSettingsDialog::~GrooveSettingsDialog() { delete ui; } void GrooveSettingsDialog::setGrooveSquences(std::vector> seqs) { seqs_ = seqs; for (size_t i = 0; i < seqs_.size(); ++i) { auto text = QString("%1: ").arg(i, 2, 16, QChar('0')).toUpper(); for (auto& g : seqs_[i]) { text = text + QString::number(g) + " "; } ui->grooveListWidget->addItem(text); } ui->grooveListWidget->setCurrentRow(0); ui->removeButton->setEnabled(ui->grooveListWidget->count() > 1); } std::vector> GrooveSettingsDialog::getGrooveSequences() { return seqs_; } void GrooveSettingsDialog::keyPressEvent(QKeyEvent* event) { switch (event->key()) { case Qt::Key_Return: case Qt::Key_Enter: // Prevent dialog from closing when it finished line edit break; default: QDialog::keyPressEvent(event); break; } } void GrooveSettingsDialog::on_addButton_clicked() { ui->grooveListWidget->addItem(QString("%1: 6 6").arg(seqs_.size(), 2, 16, QChar('0')).toUpper()); seqs_.push_back({ 6, 6}); ui->removeButton->setEnabled(true); ui->grooveListWidget->setCurrentRow(ui->grooveListWidget->count() - 1); if (ui->grooveListWidget->count() == 128) ui->addButton->setEnabled(false); } void GrooveSettingsDialog::on_removeButton_clicked() { int row = ui->grooveListWidget->currentRow(); if (row == -1) return; seqs_.erase(seqs_.begin() + row); delete ui->grooveListWidget->takeItem(row); for (int i = row; i < ui->grooveListWidget->count(); ++i) { QString text = QString::number(i) + ": "; for (auto& g : seqs_[static_cast(i)]) { text = text + QString::number(g) + " "; } ui->grooveListWidget->item(i)->setText(text); } on_grooveListWidget_currentRowChanged(ui->grooveListWidget->currentRow()); ui->addButton->setEnabled(true); if (ui->grooveListWidget->count() == 1) ui->removeButton->setEnabled(false); } void GrooveSettingsDialog::on_lineEdit_editingFinished() { int row = ui->grooveListWidget->currentRow(); if (row == -1) return; std::vector seq; QString text = ui->lineEdit->text(); while (!text.isEmpty()) { QRegularExpressionMatch m = QRegularExpression("^(\\d+)").match(text); if (m.hasMatch()) { seq.push_back(m.captured(1).toInt()); text.remove(QRegularExpression("^\\d+")); continue; } m = QRegularExpression("^ +").match(text); if (m.hasMatch()) { text.remove(QRegularExpression("^ +")); continue; } return; } if (seq.empty()) return; seqs_.at(static_cast(row)) = std::move(seq); changeSequence(row); } void GrooveSettingsDialog::on_grooveListWidget_currentRowChanged(int currentRow) { if (currentRow > -1) updateSequence(static_cast(currentRow)); } void GrooveSettingsDialog::changeSequence(int seqNum) { QString text = updateSequence(static_cast(seqNum)); ui->grooveListWidget->item(seqNum)->setText(QString("%1: ").arg(seqNum, 2, 16, QChar('0')).toUpper() + text); } QString GrooveSettingsDialog::updateSequence(size_t seqNum) { ui->seqListWidget->clear(); QString text; auto& seq = seqs_.at(seqNum); for (size_t i = 0; i < seq.size(); ++i) { ui->seqListWidget->addItem(QString("%1: %2").arg(i, 2, 16, QChar('0')).toUpper().arg(seq[i])); text = text + QString::number(seq[i]) + " "; } ui->seqListWidget->setCurrentRow(0); ui->lineEdit->setText(text); double speed = std::accumulate(seq.begin(), seq.end(), 0) / static_cast(seq.size()); ui->speedLabel->setText(tr("Speed: %1").arg(QString::number(speed, 'f', 3))); return text; } void GrooveSettingsDialog::on_upToolButton_clicked() { int curRow = ui->seqListWidget->currentRow(); if (!curRow) return; swapSequenceItem(static_cast(ui->grooveListWidget->currentRow()), curRow - 1, curRow); ui->seqListWidget->setCurrentRow(curRow - 1); } void GrooveSettingsDialog::on_downToolButton_clicked() { int curRow = ui->seqListWidget->currentRow(); if (curRow == ui->seqListWidget->count() - 1) return; swapSequenceItem(static_cast(ui->grooveListWidget->currentRow()), curRow, curRow + 1); ui->seqListWidget->setCurrentRow(curRow + 1); } void GrooveSettingsDialog::swapSequenceItem(size_t seqNum, int index1, int index2) { std::iter_swap(seqs_.at(seqNum).begin() + index1, seqs_.at(seqNum).begin() + index2); changeSequence(static_cast(seqNum)); } void GrooveSettingsDialog::on_expandPushButton_clicked() { size_t id = static_cast(ui->grooveListWidget->currentRow()); auto& ref = seqs_[id]; if (utils::find(ref, 1) != ref.end()) return; std::vector seq; for (auto v : ref) { int tmp = v / 2; seq.push_back(v - tmp); seq.push_back(tmp); } seqs_.at(id) = std::move(seq); changeSequence(static_cast(id)); } void GrooveSettingsDialog::on_shrinkPushButton_clicked() { size_t id = static_cast(ui->grooveListWidget->currentRow()); auto& ref = seqs_[id]; if (ref.size() % 2) return; std::vector seq; for (auto it = ref.begin(); it != ref.end(); it += 2) seq.push_back(*it + *(it + 1)); seqs_.at(id) = std::move(seq); changeSequence(static_cast(id)); } void GrooveSettingsDialog::on_genPushButton_clicked() { int num = ui->numeratorSpinBox->value(); int denom = ui->denominatorSpinBox->value(); if (num < denom) return; std::vector seq(static_cast(denom)); for (int i = 0; i < num * denom; i += num) seq.at(static_cast(denom - i / num - 1)) = (i + num) / denom - i / denom; seqs_.at(static_cast(ui->grooveListWidget->currentRow())) = std::move(seq); changeSequence(ui->grooveListWidget->currentRow()); } void GrooveSettingsDialog::on_padPushButton_clicked() { int pad = ui->padSpinBox->value(); size_t id = static_cast(ui->grooveListWidget->currentRow()); auto& ref = seqs_[id]; if (std::any_of(ref.begin(), ref.end(), [pad](int x) {return (x <= pad); })) return; std::vector seq; for (auto v : ref) { seq.push_back(v - pad); seq.push_back(pad); } seqs_.at(id) = std::move(seq); changeSequence(static_cast(id)); } void GrooveSettingsDialog::on_copyPushButton_clicked() { auto& seq = seqs_[static_cast(ui->grooveListWidget->currentRow())]; auto text = QString("PATTERN_COPY:3,2,%1,").arg(seq.size()); for (auto v : seq) text += QString("0F,%1,").arg(v); QApplication::clipboard()->setText(text); } BambooTracker-0.6.5/BambooTracker/gui/groove_settings_dialog.hpp000066400000000000000000000044141476276175200250310ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef GROOVE_SETTINGS_DIALOG_HPP #define GROOVE_SETTINGS_DIALOG_HPP #include #include #include namespace Ui { class GrooveSettingsDialog; } class GrooveSettingsDialog : public QDialog { Q_OBJECT public: explicit GrooveSettingsDialog(QWidget *parent = nullptr); ~GrooveSettingsDialog() override; void setGrooveSquences(std::vector> seqs); std::vector> getGrooveSequences(); protected: void keyPressEvent(QKeyEvent* event) override; private slots: void on_addButton_clicked(); void on_removeButton_clicked(); void on_lineEdit_editingFinished(); void on_grooveListWidget_currentRowChanged(int currentRow); void on_upToolButton_clicked(); void on_downToolButton_clicked(); void on_expandPushButton_clicked(); void on_shrinkPushButton_clicked(); void on_genPushButton_clicked(); void on_padPushButton_clicked(); void on_copyPushButton_clicked(); private: Ui::GrooveSettingsDialog *ui; std::vector> seqs_; void changeSequence(int seqNum); QString updateSequence(size_t seqNum); void swapSequenceItem(size_t seqNum, int index1, int index2); }; #endif // GROOVE_SETTINGS_DIALOG_HPP BambooTracker-0.6.5/BambooTracker/gui/groove_settings_dialog.ui000066400000000000000000000166411476276175200246640ustar00rootroot00000000000000 GrooveSettingsDialog 0 0 400 260 Groove Settings false 0 0 Remove 0 0 Add Speed: xx Qt::AlignCenter Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok Qt::Horizontal Sequence Copy Fxx 0 0 Qt::UpArrow Expand 3 1 100 12 0 0 / 1 100 2 Generate 0 0 Qt::DownArrow Shrink 1 Pad grooveListWidget seqListWidget upToolButton downToolButton expandPushButton shrinkPushButton genPushButton numeratorSpinBox denominatorSpinBox padPushButton padSpinBox copyPushButton lineEdit addButton removeButton buttonBox accepted() GrooveSettingsDialog accept() 248 254 157 274 buttonBox rejected() GrooveSettingsDialog reject() 316 260 286 274 BambooTracker-0.6.5/BambooTracker/gui/gui_utils.cpp000066400000000000000000000061271476276175200222730ustar00rootroot00000000000000/* * Copyright (C) 2020-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "gui_utils.hpp" #include #include "song.hpp" namespace gui_utils { QString getTrackName(SongType songType, SoundSource src, int chInSrc) { QString name; switch (src) { case SoundSource::FM: switch (songType) { case SongType::Standard: name = "FM" + QString::number(chInSrc + 1); break; case SongType::FM3chExpanded: switch (chInSrc) { case 2: name = "FM3-OP1"; break; case 6: name = "FM3-OP2"; break; case 7: name = "FM3-OP3"; break; case 8: name = "FM3-OP4"; break; default: name = "FM" + QString::number(chInSrc + 1); break; } break; } break; case SoundSource::SSG: name = "SSG" + QString::number(chInSrc + 1); break; case SoundSource::RHYTHM: switch (chInSrc) { case 0: name = "Bass drum"; break; case 1: name = "Snare drum"; break; case 2: name = "Top cymbal"; break; case 3: name = "Hi-hat"; break; case 4: name = "Tom"; break; case 5: name = "Rim shot"; break; } break; case SoundSource::ADPCM: name = "ADPCM"; break; } return name; } std::vector adaptVisibleTrackList(const std::vector list, const SongType prevType, const SongType curType) { if (prevType == curType) return list; std::vector tracks; if (prevType == SongType::Standard) { for (const int& track : list) { switch (track) { case 0: case 1: tracks.push_back(track); break; case 2: { std::vector tmp { 2, 3, 4, 5 }; std::copy(tmp.begin(), tmp.end(), std::back_inserter(tracks)); break; } default: tracks.push_back(track + 3); break; } } } else { // FM3chExpanded bool hasPushed3 = false; for (const int& track : list) { switch (track) { case 0: case 1: tracks.push_back(track); break; case 2: case 3: case 4: case 5: if (!hasPushed3) { tracks.push_back(2); hasPushed3 = true; } break; default: tracks.push_back(track - 3); break; } } } return tracks; } } BambooTracker-0.6.5/BambooTracker/gui/gui_utils.hpp000066400000000000000000000034761476276175200223040ustar00rootroot00000000000000/* * Copyright (C) 2020-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef GUI_UTILS_HPP #define GUI_UTILS_HPP #include #include #include #include #include "bamboo_tracker_defs.hpp" enum class SongType; namespace gui_utils { QString getTrackName(SongType songType, SoundSource src, int chInSrc); inline QString utf8ToQString(const std::string& str) { return QString::fromUtf8(str.c_str(), static_cast(str.length())); } inline QKeySequence strToKeySeq(std::string str) { return QKeySequence(utf8ToQString(str)); } std::vector adaptVisibleTrackList(const std::vector list, const SongType prevType, const SongType curType); } namespace io { const QString ORGANIZATION_NAME = "BambooTracker"; } #endif // GUI_UTILS_HPP BambooTracker-0.6.5/BambooTracker/gui/hide_tracks_dialog.cpp000066400000000000000000000061251476276175200240640ustar00rootroot00000000000000/* * Copyright (C) 2020-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "hide_tracks_dialog.hpp" #include "ui_hide_tracks_dialog.h" #include #include #include "song.hpp" #include "gui/gui_utils.hpp" HideTracksDialog::HideTracksDialog(const SongStyle& style, const std::vector& tracks, QWidget *parent) : QDialog(parent), ui(new Ui::HideTracksDialog) { ui->setupUi(this); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); for (const auto& attrib : style.trackAttribs) { auto item = new QListWidgetItem(gui_utils::getTrackName(style.type, attrib.source, attrib.channelInSource), ui->listWidget); int n = attrib.number; item->setData(Qt::UserRole, n); // Track numbers are sorted bool exists = std::any_of(tracks.begin(), tracks.end(), [n](int t) { return n == t; }); item->setCheckState(exists ? Qt::Checked : Qt::Unchecked); } QObject::connect(ui->listWidget, &QListWidget::itemChanged, this, [&](QListWidgetItem* item) { if (item->checkState() == Qt::Unchecked) { --checkCounter_; if (!checkCounter_) ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); } else { ++checkCounter_; ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); } }); checkCounter_ = style.trackAttribs.size(); } HideTracksDialog::~HideTracksDialog() { delete ui; } std::vector HideTracksDialog::getVisibleTracks() const { std::vector tracks; for (int i = 0; i < ui->listWidget->count(); ++i) { auto item = ui->listWidget->item(i); if (item->checkState() == Qt::Checked) tracks.push_back(item->data(Qt::UserRole).toInt()); } return tracks; } void HideTracksDialog::on_reversePushButton_clicked() { for (int i = 0; i < ui->listWidget->count(); ++i) { auto item = ui->listWidget->item(i); item->setCheckState((item->checkState() == Qt::Checked) ? Qt::Unchecked : Qt::Checked); } } void HideTracksDialog::on_checkAllPushButton_clicked() { for (int i = 0; i < ui->listWidget->count(); ++i) { auto item = ui->listWidget->item(i); item->setCheckState(Qt::Checked); } } BambooTracker-0.6.5/BambooTracker/gui/hide_tracks_dialog.hpp000066400000000000000000000033031476276175200240640ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef HIDE_TRACKS_DIALOG_HPP #define HIDE_TRACKS_DIALOG_HPP #include #include struct SongStyle; namespace Ui { class HideTracksDialog; } class HideTracksDialog : public QDialog { Q_OBJECT public: explicit HideTracksDialog(const SongStyle& style, const std::vector& tracks, QWidget *parent = nullptr); ~HideTracksDialog() override; std::vector getVisibleTracks() const; private slots: void on_reversePushButton_clicked(); void on_checkAllPushButton_clicked(); private: Ui::HideTracksDialog *ui; size_t checkCounter_; }; #endif // HIDE_TRACKS_DIALOG_HPP BambooTracker-0.6.5/BambooTracker/gui/hide_tracks_dialog.ui000066400000000000000000000046701476276175200237220ustar00rootroot00000000000000 HideTracksDialog 0 0 174 300 Hide Tracks Check All Reverse Set the visibility of tracks: Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok Qt::Horizontal listWidget reversePushButton checkAllPushButton buttonBox accepted() HideTracksDialog accept() 248 254 157 274 buttonBox rejected() HideTracksDialog reject() 316 260 286 274 BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/000077500000000000000000000000001476276175200233335ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/adpcm_address_spin_box.cpp000066400000000000000000000103641476276175200305350ustar00rootroot00000000000000/* * Copyright (C) 2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "adpcm_address_spin_box.hpp" #include namespace { constexpr int ADDR_SHIFT = 5; } namespace { namespace start { constexpr int addressToPosition(int a) { return a << (ADDR_SHIFT + 1); } constexpr int bytesToPosition(int b) { return addressToPosition(b >> ADDR_SHIFT); } constexpr int positionToAddress(int p) { return p >> (ADDR_SHIFT + 1); } } class StartAddressValidator : public QIntValidator { public: StartAddressValidator(int bottom, int top, QObject *parent = nullptr) : QIntValidator(bottom, top, parent) {} QValidator::State validate(QString& input, int& pos) const override { auto state = QIntValidator::validate(input, pos); if (state == QIntValidator::Invalid) { return QValidator::Invalid; } return !(input.toInt() % (1 << (ADDR_SHIFT + 1))) ? QValidator::Acceptable : QValidator::Intermediate; } }; } AdpcmStartAddressSpinBox::AdpcmStartAddressSpinBox(QWidget* parent) : QSpinBox(parent) { setSingleStep(1 << (ADDR_SHIFT + 1)); setMinimum(start::addressToPosition(0)); setValue(minimum()); setMaximum(minimum()); } int AdpcmStartAddressSpinBox::valueByAddress() const { return start::positionToAddress(value()); } void AdpcmStartAddressSpinBox::setValueByAddress(int a) { setValue(start::addressToPosition(a)); } void AdpcmStartAddressSpinBox::setValueByBytes(int b) { setValue(start::bytesToPosition(b)); } void AdpcmStartAddressSpinBox::setMaximumByBytes(int b) { setMaximum(start::bytesToPosition(b)); } QValidator::State AdpcmStartAddressSpinBox::validate(QString& text, int& pos) const { return StartAddressValidator(minimum(), maximum()).validate(text, pos); } namespace { namespace stop { constexpr int addressToPosition(int a) { // (((a << ADDR_SHIFT) & 31) << 1) + 1 return (a << (ADDR_SHIFT + 1)) + 63; } constexpr int bytesToPosition(int b) { return addressToPosition(b >> ADDR_SHIFT); } constexpr int positionToAddress(int p) { return p >> (ADDR_SHIFT + 1); } } class StopAddressValidator : public QIntValidator { public: StopAddressValidator(int bottom, int top, QObject *parent = nullptr) : QIntValidator(bottom, top, parent) {} QValidator::State validate(QString& input, int& pos) const override { auto state = QIntValidator::validate(input, pos); if (state == QIntValidator::Invalid) { return QValidator::Invalid; } return !((input.toInt() + 1) % (1 << (ADDR_SHIFT + 1))) ? QValidator::Acceptable : QValidator::Intermediate; } }; } AdpcmStopAddressSpinBox::AdpcmStopAddressSpinBox(QWidget* parent) : QSpinBox(parent) { setSingleStep(1 << (ADDR_SHIFT + 1)); setMinimum(stop::addressToPosition(0)); setValue(minimum()); setMaximum(minimum()); } int AdpcmStopAddressSpinBox::valueByAddress() const { return stop::positionToAddress(value()); } void AdpcmStopAddressSpinBox::setValueByAddress(int a) { setValue(stop::addressToPosition(a)); } void AdpcmStopAddressSpinBox::setValueByBytes(int b) { setValue(stop::bytesToPosition(b)); } void AdpcmStopAddressSpinBox::setMaximumByBytes(int b) { setMaximum(stop::bytesToPosition(b)); } QValidator::State AdpcmStopAddressSpinBox::validate(QString& text, int& pos) const { return StopAddressValidator(minimum(), maximum()).validate(text, pos); } BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/adpcm_address_spin_box.hpp000066400000000000000000000034041476276175200305370ustar00rootroot00000000000000/* * Copyright (C) 2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include class AdpcmStartAddressSpinBox : public QSpinBox { public: AdpcmStartAddressSpinBox(QWidget* parent = nullptr); int valueByAddress() const; void setValueByAddress(int a); void setValueByBytes(int b); void setMaximumByBytes(int b); protected: QValidator::State validate(QString& text, int& pos) const override; }; class AdpcmStopAddressSpinBox : public QSpinBox { public: AdpcmStopAddressSpinBox(QWidget* parent = nullptr); int valueByAddress() const; void setValueByAddress(int a); void setValueByBytes(int b); void setMaximumByBytes(int b); protected: QValidator::State validate(QString& text, int& pos) const override; }; BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/adpcm_drumkit_editor.cpp000066400000000000000000000264411476276175200302370ustar00rootroot00000000000000/* * Copyright (C) 2020-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "adpcm_drumkit_editor.hpp" #include "ui_adpcm_drumkit_editor.h" #include #include #include "instrument.hpp" #include "note.hpp" #include "gui/event_guard.hpp" #include "gui/jam_layout.hpp" #include "gui/note_name_manager.hpp" #include "gui/slider_style.hpp" namespace { const char* PAN_TEXT[] = { QT_TRANSLATE_NOOP("Panning", "Left"), QT_TRANSLATE_NOOP("Panning", "Center"), QT_TRANSLATE_NOOP("Panning", "Right") }; constexpr int PAN_UI2INTRNL[] = { PanType::LEFT, PanType::CENTER, PanType::RIGHT }; inline int convertPanInternalToUi(int intrPan) { return std::distance(std::begin(PAN_UI2INTRNL), std::find(std::begin(PAN_UI2INTRNL), std::end(PAN_UI2INTRNL), intrPan)); } } AdpcmDrumkitEditor::AdpcmDrumkitEditor(int num, QWidget* parent) : InstrumentEditor(num, parent), ui(new Ui::AdpcmDrumkitEditor), isIgnoreEvent_(false), hasShown_(false) { ui->setupUi(this); ui->keyTreeWidget->header()->setDefaultSectionSize(40); // Rearrange header widths NoteNameManager& nnm = NoteNameManager::getManager(); for (int i = 0; i < Note::NOTE_NUMBER_RANGE; ++i) { ui->keyTreeWidget->addTopLevelItem( new QTreeWidgetItem({ nnm.getNoteString(i), "-", "-" })); } //========== Sample ==========// QObject::connect(ui->sampleEditor, &ADPCMSampleEditor::modified, this, [&] { emit modified(); }); QObject::connect(ui->sampleEditor, &ADPCMSampleEditor::sampleNumberChanged, this, [&](int n) { bt_.lock()->setInstrumentDrumkitSample(instNum_, ui->keyTreeWidget->currentIndex().row(), n); setInstrumentSampleParameters(ui->keyTreeWidget->currentIndex().row()); emit sampleNumberChanged(); emit modified(); if (config_.lock()->getWriteOnlyUsedSamples()) { emit sampleAssignRequested(); } }, Qt::DirectConnection); QObject::connect(ui->sampleEditor, &ADPCMSampleEditor::sampleParameterChanged, this, [&](int sampNum) { emit sampleParameterChanged(sampNum, instNum_); }); QObject::connect(ui->sampleEditor, &ADPCMSampleEditor::sampleAssignRequested, this, [&] { emit sampleAssignRequested(); }); QObject::connect(ui->sampleEditor, &ADPCMSampleEditor::sampleMemoryChanged, this, [&] { emit sampleMemoryChanged(); }); //========== Pan ==========// ui->panHorizontalSlider->setStyle(SliderStyle::instance()); ui->panHorizontalSlider->installEventFilter(this); ui->panPosLabel->setText(QCoreApplication::translate("Panning", PAN_TEXT[ui->panHorizontalSlider->value()])); } AdpcmDrumkitEditor::~AdpcmDrumkitEditor() { delete ui; } SoundSource AdpcmDrumkitEditor::getSoundSource() const { return SoundSource::ADPCM; } InstrumentType AdpcmDrumkitEditor::getInstrumentType() const { return InstrumentType::Drumkit; } void AdpcmDrumkitEditor::updateBySettingCore() { ui->sampleEditor->setCore(bt_); updateInstrumentParameters(); } void AdpcmDrumkitEditor::updateBySettingConfiguration() { ui->sampleEditor->setConfiguration(config_); } void AdpcmDrumkitEditor::updateBySettingColorPalette() { ui->sampleEditor->setColorPalette(palette_); } void AdpcmDrumkitEditor::updateInstrumentParameters() { Ui::EventGuard eg(isIgnoreEvent_); updateWindowTitle(); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instKit = dynamic_cast(inst.get()); for (const auto& key : instKit->getAssignedKeys()) { setInstrumentSampleParameters(key); } } /********** Events **********/ bool AdpcmDrumkitEditor::eventFilter(QObject* watched, QEvent* event) { if (watched == ui->panHorizontalSlider) { if (event->type() == QEvent::Wheel) { auto e = dynamic_cast(event); if (e->angleDelta().y() > 0) ui->panHorizontalSlider->setValue(ui->panHorizontalSlider->value() + 1); else if (e->angleDelta().y() < 0) ui->panHorizontalSlider->setValue(ui->panHorizontalSlider->value() - 1); return true; } } return QWidget::eventFilter(watched, event); } void AdpcmDrumkitEditor::showEvent(QShowEvent*) { if (!hasShown_) { ui->keyTreeWidget->setCurrentItem(ui->keyTreeWidget->topLevelItem(Note::DEFAULT_NOTE_NUM)); ui->keyTreeWidget->scrollTo(ui->keyTreeWidget->model()->index(Note::DEFAULT_NOTE_NUM, 0), QAbstractItemView::PositionAtTop); int x = config_.lock()->getInstrumentDrumkitWindowHorizontalSplit(); if (x == -1) { config_.lock()->setInstrumentDrumkitWindowHorizontalSplit(ui->splitter->sizes().at(0)); } else { ui->splitter->setSizes({ x, ui->splitter->width() - ui->splitter->handleWidth() - x }); } } hasShown_ = true; } // MUST DIRECT CONNECTION void AdpcmDrumkitEditor::keyPressEvent(QKeyEvent *event) { // General keys switch (event->key()) { case Qt::Key_Escape: close(); break; default: // For jam key on if (!event->isAutoRepeat()) { // Musical keyboard Qt::Key qtKey = static_cast(event->key()); try { JamKey jk = getJamKeyFromLayoutMapping(qtKey, config_); emit jamKeyOnEvent(jk); } catch (std::invalid_argument&) {} } break; } } // MUST DIRECT CONNECTION void AdpcmDrumkitEditor::keyReleaseEvent(QKeyEvent *event) { // For jam key off if (!event->isAutoRepeat()) { Qt::Key qtKey = static_cast(event->key()); try { JamKey jk = getJamKeyFromLayoutMapping(qtKey, config_); emit jamKeyOffEvent(jk); } catch (std::invalid_argument&) {} } } /********** Slots **********/ void AdpcmDrumkitEditor::on_keyTreeWidget_currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*) { Ui::EventGuard eg(isIgnoreEvent_); int key = ui->keyTreeWidget->currentIndex().row(); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instKit = dynamic_cast(inst.get()); bool enabled = instKit->getSampleEnabled(key); ui->sampleGroupBox->setChecked(enabled); if (enabled) { setInstrumentSampleParameters(key); ui->pitchSpinBox->setValue(instKit->getPitch(key)); ui->panHorizontalSlider->setValue(convertPanInternalToUi(instKit->getPan(key))); } } void AdpcmDrumkitEditor::on_splitter_splitterMoved(int pos, int) { config_.lock()->setInstrumentDrumkitWindowHorizontalSplit(pos); } //--- Pitch void AdpcmDrumkitEditor::on_pitchSpinBox_valueChanged(int arg1) { int key = ui->keyTreeWidget->currentIndex().row(); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instKit = dynamic_cast(inst.get()); if (instKit->getSampleEnabled(key)) { bt_.lock()->setInstrumentDrumkitPitch(instNum_, key, arg1); ui->keyTreeWidget->currentItem()->setText(2, QString::number(arg1)); emit modified(); } } //--- Pan void AdpcmDrumkitEditor::on_panHorizontalSlider_valueChanged(int value) { const QString text = QCoreApplication::translate("Panning", PAN_TEXT[value]); ui->panPosLabel->setText(text); int key = ui->keyTreeWidget->currentIndex().row(); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instKit = dynamic_cast(inst.get()); if (instKit->getSampleEnabled(key)) { bt_.lock()->setInstrumentDrumkitPan(instNum_, key, PAN_UI2INTRNL[value]); ui->keyTreeWidget->currentItem()->setText(3, text); emit modified(); } } //--- Sample void AdpcmDrumkitEditor::setInstrumentSampleParameters(int key) { std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instKit = dynamic_cast(inst.get()); QTreeWidgetItem* item = ui->keyTreeWidget->topLevelItem(key); if (instKit->getSampleEnabled(key)) { int sampNum = instKit->getSampleNumber(key); ui->sampleEditor->setInstrumentSampleParameters( sampNum, instKit->isSampleRepeatable(key), instKit->getSampleRepeatRange(key), instKit->getSampleRootKeyNumber(key), instKit->getSampleRootDeltaN(key), instKit->getSampleStartAddress(key), instKit->getSampleStopAddress(key), instKit->getRawSample(key)); item->setText(1, QString::number(sampNum)); item->setText(2, QString::number(instKit->getPitch(key))); item->setText(3, QCoreApplication::translate("Panning", PAN_TEXT[convertPanInternalToUi(instKit->getPan(key))])); } else { ui->sampleEditor->setInstrumentSampleParameters( 0, bt_.lock()->getSampleADPCMRepeatEnabled(0), bt_.lock()->getSampleADPCMRepeatRange(0), bt_.lock()->getSampleADPCMRootKeyNumber(0), bt_.lock()->getSampleADPCMRootDeltaN(0), bt_.lock()->getSampleADPCMStartAddress(0), bt_.lock()->getSampleADPCMStopAddress(0), bt_.lock()->getSampleADPCMRawSample(0)); item->setText(1, "-"); item->setText(2, "-"); item->setText(3, "-"); } } /********** Slots **********/ void AdpcmDrumkitEditor::onSampleNumberChanged() { ui->sampleEditor->onSampleNumberChanged(); } void AdpcmDrumkitEditor::onSampleParameterChanged(int sampNum) { if (ui->sampleEditor->getSampleNumber() == sampNum) { setInstrumentSampleParameters(ui->keyTreeWidget->currentIndex().row()); } } void AdpcmDrumkitEditor::onSampleMemoryUpdated() { std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instKit = dynamic_cast(inst.get()); int key = ui->keyTreeWidget->currentIndex().row(); if (instKit->getSampleEnabled(key)) { ui->sampleEditor->onSampleMemoryUpdated(instKit->getSampleStartAddress(key), instKit->getSampleStopAddress(key)); } else { // Clear addresses ui->sampleEditor->onSampleMemoryUpdated(0, 0); } } void AdpcmDrumkitEditor::on_sampleGroupBox_clicked(bool checked) { Ui::EventGuard eg(isIgnoreEvent_); int key = ui->keyTreeWidget->currentIndex().row(); bt_.lock()->setInstrumentDrumkitSampleEnabled(instNum_, key, checked); if (checked) { ui->pitchSpinBox->setValue(0); ui->panHorizontalSlider->setValue(convertPanInternalToUi(PanType::CENTER)); setInstrumentSampleParameters(key); } else { // Clear parameters ui->sampleEditor->setInstrumentSampleParameters( 0, bt_.lock()->getSampleADPCMRepeatEnabled(0), bt_.lock()->getSampleADPCMRepeatRange(0), bt_.lock()->getSampleADPCMRootKeyNumber(0), bt_.lock()->getSampleADPCMRootDeltaN(0), bt_.lock()->getSampleADPCMStartAddress(0), bt_.lock()->getSampleADPCMStopAddress(0), bt_.lock()->getSampleADPCMRawSample(0)); auto item = ui->keyTreeWidget->currentItem(); item->setText(1, "-"); item->setText(2, "-"); item->setText(3, "-"); } emit sampleNumberChanged(); emit modified(); } BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/adpcm_drumkit_editor.hpp000066400000000000000000000061161476276175200302410ustar00rootroot00000000000000/* * Copyright (C) 2020-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include "gui/instrument_editor/instrument_editor.hpp" namespace Ui { class AdpcmDrumkitEditor; } class AdpcmDrumkitEditor final : public InstrumentEditor { Q_OBJECT public: explicit AdpcmDrumkitEditor(int num, QWidget* parent = nullptr); ~AdpcmDrumkitEditor() override; /** * @brief Return sound source of instrument related to this dialog. * @return ADPCM. */ SoundSource getSoundSource() const override; /** * @brief Return instrument type of instrument related to this dialog. * @return Drumkit. */ InstrumentType getInstrumentType() const override; void updateByConfigurationChange() override {} protected: bool eventFilter(QObject* watched, QEvent* event) override; void showEvent(QShowEvent*) override; void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; private: Ui::AdpcmDrumkitEditor *ui; bool isIgnoreEvent_; bool hasShown_; // Drumkit specific process called in settiing core / cofiguration / color palette. void updateBySettingCore() override; void updateBySettingConfiguration() override; void updateBySettingColorPalette() override; void updateInstrumentParameters(); private slots: void on_keyTreeWidget_currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*); void on_splitter_splitterMoved(int pos, int); //========== Pitch ==========// private slots: void on_pitchSpinBox_valueChanged(int arg1); //========== Pan ==========// private slots: void on_panHorizontalSlider_valueChanged(int value); //========== Sample ==========// signals: void sampleNumberChanged(); void sampleParameterChanged(int sampNum, int fromInstNum); void sampleAssignRequested(); void sampleMemoryChanged(); public slots: void onSampleNumberChanged(); void onSampleParameterChanged(int sampNum); void onSampleMemoryUpdated(); private slots: void on_sampleGroupBox_clicked(bool checked); private: void setInstrumentSampleParameters(int key); }; BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/adpcm_drumkit_editor.ui000066400000000000000000000143451476276175200300720ustar00rootroot00000000000000 AdpcmDrumkitEditor 0 0 620 450 Qt::Horizontal Sample assignment 0 0 0 0 16777215 16777215 Qt::ScrollBarAlwaysOff false true 10 40 true Key # Pitch Pan Pitch 0 0 -95 95 Panning QFrame::WinPanel QFrame::Sunken Qt::AlignCenter 0 0 2 2 1 Qt::Horizontal QSlider::TicksAbove 0 0 Sample true false 0 0 ADPCMSampleEditor QWidget
gui/instrument_editor/adpcm_sample_editor.hpp
1
keyTreeWidget pitchSpinBox panHorizontalSlider sampleGroupBox
BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/adpcm_instrument_editor.cpp000066400000000000000000000635571476276175200310010ustar00rootroot00000000000000/* * Copyright (C) 2020-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "adpcm_instrument_editor.hpp" #include "ui_adpcm_instrument_editor.h" #include #include "instrument.hpp" #include "gui/event_guard.hpp" #include "gui/jam_layout.hpp" #include "gui/instrument_editor/instrument_editor_utils.hpp" AdpcmInstrumentEditor::AdpcmInstrumentEditor(int num, QWidget* parent) : InstrumentEditor(num, parent), ui(new Ui::AdpcmInstrumentEditor), isIgnoreEvent_(false) { ui->setupUi(this); //========== Sample ==========// QObject::connect(ui->sampleEditor, &ADPCMSampleEditor::modified, this, [&] { emit modified(); }); QObject::connect(ui->sampleEditor, &ADPCMSampleEditor::sampleNumberChanged, this, [&](int n) { bt_.lock()->setInstrumentADPCMSample(instNum_, n); setInstrumentSampleParameters(); emit sampleNumberChanged(); emit modified(); if (config_.lock()->getWriteOnlyUsedSamples()) { emit sampleAssignRequested(); } }, Qt::DirectConnection); QObject::connect(ui->sampleEditor, &ADPCMSampleEditor::sampleParameterChanged, this, [&](int sampNum) { emit sampleParameterChanged(sampNum, instNum_); }); QObject::connect(ui->sampleEditor, &ADPCMSampleEditor::sampleAssignRequested, this, [&] { emit sampleAssignRequested(); }); QObject::connect(ui->sampleEditor, &ADPCMSampleEditor::sampleMemoryChanged, this, [&] { emit sampleMemoryChanged(); }); //========== Envelope ==========// ui->envEditor->setMaximumDisplayedRowCount(64); ui->envEditor->setDefaultRow(255); ui->envEditor->setLabelDiaplayMode(true); for (int i = 0; i < 256; ++i) { ui->envEditor->AddRow(QString::number(i), false); } ui->envEditor->autoFitLabelWidth(); ui->envEditor->setUpperRow(255); ui->envEditor->setMultipleReleaseState(true); ui->envEditor->setPermittedReleaseTypes( VisualizedInstrumentMacroEditor::PermittedReleaseFlag::ABSOLUTE_RELEASE | VisualizedInstrumentMacroEditor::PermittedReleaseFlag::RELATIVE_RELEASE | VisualizedInstrumentMacroEditor::PermittedReleaseFlag::FIXED_RELEASE); QObject::connect(ui->envEditor, &VisualizedInstrumentMacroEditor::sequenceDataAdded, this, [&](int row, int) { if (!isIgnoreEvent_) { bt_.lock()->addEnvelopeADPCMSequenceData(ui->envNumSpinBox->value(), row); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->envEditor, &VisualizedInstrumentMacroEditor::sequenceDataRemoved, this, [&] { if (!isIgnoreEvent_) { bt_.lock()->removeEnvelopeADPCMSequenceData(ui->envNumSpinBox->value()); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->envEditor, &VisualizedInstrumentMacroEditor::sequenceDataChanged, this, [&](int row, int col) { if (!isIgnoreEvent_) { bt_.lock()->setEnvelopeADPCMSequenceData(ui->envNumSpinBox->value(), col, row); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->envEditor, &VisualizedInstrumentMacroEditor::loopAdded, this, [&](InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->addEnvelopeADPCMLoop(ui->envNumSpinBox->value(), loop); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->envEditor, &VisualizedInstrumentMacroEditor::loopRemoved, this, [&](int begin, int end) { if (!isIgnoreEvent_) { bt_.lock()->removeEnvelopeADPCMLoop(ui->envNumSpinBox->value(), begin, end); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->envEditor, &VisualizedInstrumentMacroEditor::loopCleared, this, [&] { if (!isIgnoreEvent_) { bt_.lock()->clearEnvelopeADPCMLoops(ui->envNumSpinBox->value()); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->envEditor, &VisualizedInstrumentMacroEditor::loopChanged, this, [&](int prevBegin, int prevEnd, InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->changeEnvelopeADPCMLoop(ui->envNumSpinBox->value(), prevBegin, prevEnd, loop); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->envEditor, &VisualizedInstrumentMacroEditor::releaseChanged, this, [&](InstrumentSequenceRelease release) { if (!isIgnoreEvent_) { bt_.lock()->setEnvelopeADPCMRelease(ui->envNumSpinBox->value(), release); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); //========== Arpeggio ==========// ui->arpTypeComboBox->addItem(tr("Absolute"), static_cast(SequenceType::AbsoluteSequence)); ui->arpTypeComboBox->addItem(tr("Fixed"), static_cast(SequenceType::FixedSequence)); ui->arpTypeComboBox->addItem(tr("Relative"), static_cast(SequenceType::RelativeSequence)); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::sequenceDataAdded, this, [&](int row, int) { if (!isIgnoreEvent_) { bt_.lock()->addArpeggioADPCMSequenceData(ui->arpNumSpinBox->value(), row); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::sequenceDataRemoved, this, [&] { if (!isIgnoreEvent_) { bt_.lock()->removeArpeggioADPCMSequenceData(ui->arpNumSpinBox->value()); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::sequenceDataChanged, this, [&](int row, int col) { if (!isIgnoreEvent_) { bt_.lock()->setArpeggioADPCMSequenceData(ui->arpNumSpinBox->value(), col, row); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::loopAdded, this, [&](InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->addArpeggioADPCMLoop(ui->arpNumSpinBox->value(), loop); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::loopRemoved, this, [&](int begin, int end) { if (!isIgnoreEvent_) { bt_.lock()->removeArpeggioADPCMLoop(ui->arpNumSpinBox->value(), begin, end); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::loopCleared, this, [&] { if (!isIgnoreEvent_) { bt_.lock()->clearArpeggioADPCMLoops(ui->arpNumSpinBox->value()); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::loopChanged, this, [&](int prevBegin, int prevEnd, InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->changeArpeggioADPCMLoop(ui->arpNumSpinBox->value(), prevBegin, prevEnd, loop); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::releaseChanged, this, [&](InstrumentSequenceRelease release) { if (!isIgnoreEvent_) { bt_.lock()->setArpeggioADPCMRelease(ui->arpNumSpinBox->value(), release); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->arpTypeComboBox, static_cast(&QComboBox::currentIndexChanged), this, &AdpcmInstrumentEditor::onArpeggioTypeChanged); //========== Pitch ==========// ui->ptEditor->setMaximumDisplayedRowCount(15); ui->ptEditor->setDefaultRow(127); ui->ptEditor->setLabelDiaplayMode(true); for (int i = 0; i < 255; ++i) { ui->ptEditor->AddRow(QString::asprintf("%+d", i - 127), false); } ui->ptEditor->autoFitLabelWidth(); ui->ptEditor->setUpperRow(134); ui->ptEditor->setMMLDisplay0As(-127); ui->ptTypeComboBox->addItem(tr("Absolute"), static_cast(SequenceType::AbsoluteSequence)); ui->ptTypeComboBox->addItem(tr("Relative"), static_cast(SequenceType::RelativeSequence)); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::sequenceDataAdded, this, [&](int row, int) { if (!isIgnoreEvent_) { bt_.lock()->addPitchADPCMSequenceData(ui->ptNumSpinBox->value(), row); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::sequenceDataRemoved, this, [&] { if (!isIgnoreEvent_) { bt_.lock()->removePitchADPCMSequenceData(ui->ptNumSpinBox->value()); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::sequenceDataChanged, this, [&](int row, int col) { if (!isIgnoreEvent_) { bt_.lock()->setPitchADPCMSequenceData(ui->ptNumSpinBox->value(), col, row); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::loopAdded, this, [&](InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->addPitchADPCMLoop(ui->ptNumSpinBox->value(), loop); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::loopRemoved, this, [&](int begin, int end) { if (!isIgnoreEvent_) { bt_.lock()->removePitchADPCMLoop(ui->ptNumSpinBox->value(), begin, end); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::loopCleared, this, [&] { if (!isIgnoreEvent_) { bt_.lock()->clearPitchADPCMLoops(ui->ptNumSpinBox->value()); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::loopChanged, this, [&](int prevBegin, int prevEnd, InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->changePitchADPCMLoop(ui->ptNumSpinBox->value(), prevBegin, prevEnd, loop); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::releaseChanged, this, [&](InstrumentSequenceRelease release) { if (!isIgnoreEvent_) { bt_.lock()->setPitchADPCMRelease(ui->ptNumSpinBox->value(), release); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->ptTypeComboBox, static_cast(&QComboBox::currentIndexChanged), this, &AdpcmInstrumentEditor::onPitchTypeChanged); //========== Pan ==========// QObject::connect(ui->panEditor, &VisualizedInstrumentMacroEditor::sequenceDataAdded, this, [&](int row, int) { if (!isIgnoreEvent_) { bt_.lock()->addPanADPCMSequenceData(ui->panNumSpinBox->value(), row); emit panParameterChanged(ui->panNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->panEditor, &VisualizedInstrumentMacroEditor::sequenceDataRemoved, this, [&] { if (!isIgnoreEvent_) { bt_.lock()->removePanADPCMSequenceData(ui->panNumSpinBox->value()); emit panParameterChanged(ui->panNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->panEditor, &VisualizedInstrumentMacroEditor::sequenceDataChanged, this, [&](int row, int col) { if (!isIgnoreEvent_) { bt_.lock()->setPanADPCMSequenceData(ui->panNumSpinBox->value(), col, row); emit panParameterChanged(ui->panNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->panEditor, &VisualizedInstrumentMacroEditor::loopAdded, this, [&](InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->addPanADPCMLoop(ui->panNumSpinBox->value(), loop); emit panParameterChanged(ui->panNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->panEditor, &VisualizedInstrumentMacroEditor::loopRemoved, this, [&](int begin, int end) { if (!isIgnoreEvent_) { bt_.lock()->removePanADPCMLoop(ui->panNumSpinBox->value(), begin, end); emit panParameterChanged(ui->panNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->panEditor, &VisualizedInstrumentMacroEditor::loopCleared, this, [&] { if (!isIgnoreEvent_) { bt_.lock()->clearPanADPCMLoops(ui->panNumSpinBox->value()); emit panParameterChanged(ui->panNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->panEditor, &VisualizedInstrumentMacroEditor::loopChanged, this, [&](int prevBegin, int prevEnd, InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->changePanADPCMLoop(ui->panNumSpinBox->value(), prevBegin, prevEnd, loop); emit panParameterChanged(ui->panNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->panEditor, &VisualizedInstrumentMacroEditor::releaseChanged, this, [&](InstrumentSequenceRelease release) { if (!isIgnoreEvent_) { bt_.lock()->setPanADPCMRelease(ui->panNumSpinBox->value(), release); emit panParameterChanged(ui->panNumSpinBox->value(), instNum_); emit modified(); } }); } AdpcmInstrumentEditor::~AdpcmInstrumentEditor() { delete ui; } SoundSource AdpcmInstrumentEditor::getSoundSource() const { return SoundSource::ADPCM; } InstrumentType AdpcmInstrumentEditor::getInstrumentType() const { return InstrumentType::ADPCM; } void AdpcmInstrumentEditor::updateByConfigurationChange() { ui->sampleEditor->onNoteNamesUpdated(); ui->arpEditor->onNoteNamesUpdated(); } void AdpcmInstrumentEditor::updateBySettingCore() { ui->sampleEditor->setCore(bt_); updateInstrumentParameters(); } void AdpcmInstrumentEditor::updateBySettingConfiguration() { ui->sampleEditor->setConfiguration(config_); } void AdpcmInstrumentEditor::updateBySettingColorPalette() { ui->sampleEditor->setColorPalette(palette_); ui->envEditor->setColorPalette(palette_); ui->arpEditor->setColorPalette(palette_); ui->ptEditor->setColorPalette(palette_); ui->panEditor->setColorPalette(palette_); } void AdpcmInstrumentEditor::updateInstrumentParameters() { Ui::EventGuard eg(isIgnoreEvent_); updateWindowTitle(); setInstrumentSampleParameters(); setInstrumentEnvelopeParameters(); setInstrumentArpeggioParameters(); setInstrumentPitchParameters(); setInstrumentPanParameters(); } /********** Events **********/ // MUST DIRECT CONNECTION void AdpcmInstrumentEditor::keyPressEvent(QKeyEvent *event) { // General keys switch (event->key()) { case Qt::Key_Escape: close(); break; default: // For jam key on if (!event->isAutoRepeat()) { // Musical keyboard Qt::Key qtKey = static_cast(event->key()); try { JamKey jk = getJamKeyFromLayoutMapping(qtKey, config_); emit jamKeyOnEvent(jk); } catch (std::invalid_argument&) {} } break; } } // MUST DIRECT CONNECTION void AdpcmInstrumentEditor::keyReleaseEvent(QKeyEvent *event) { // For jam key off if (!event->isAutoRepeat()) { Qt::Key qtKey = static_cast(event->key()); try { JamKey jk = getJamKeyFromLayoutMapping(qtKey, config_); emit jamKeyOffEvent(jk); } catch (std::invalid_argument&) {} } } //--- Sample void AdpcmInstrumentEditor::setInstrumentSampleParameters() { std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instADPCM = dynamic_cast(inst.get()); ui->sampleEditor->setInstrumentSampleParameters( instADPCM->getSampleNumber(), instADPCM->isSampleRepeatable(), instADPCM->getSampleRepeatRange(), instADPCM->getSampleRootKeyNumber(), instADPCM->getSampleRootDeltaN(), instADPCM->getSampleStartAddress(), instADPCM->getSampleStopAddress(), instADPCM->getRawSample()); } /********** Slots **********/ void AdpcmInstrumentEditor::onSampleNumberChanged() { ui->sampleEditor->onSampleNumberChanged(); } void AdpcmInstrumentEditor::onSampleParameterChanged(int sampNum) { if (ui->sampleEditor->getSampleNumber() == sampNum) { setInstrumentSampleParameters(); } } void AdpcmInstrumentEditor::onSampleMemoryUpdated() { std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instADPCM = dynamic_cast(inst.get()); ui->sampleEditor->onSampleMemoryUpdated(instADPCM->getSampleStartAddress(), instADPCM->getSampleStopAddress()); } //--- Envelope void AdpcmInstrumentEditor::setInstrumentEnvelopeParameters() { Ui::EventGuard ev(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instADPCM = dynamic_cast(inst.get()); ui->envNumSpinBox->setValue(instADPCM->getEnvelopeNumber()); ui->envEditor->clearData(); for (auto& unit : instADPCM->getEnvelopeSequence()) { ui->envEditor->addSequenceData(unit.data); } for (auto& loop : instADPCM->getEnvelopeLoopRoot().getAllLoops()) { ui->envEditor->addLoop(loop.getBeginPos(), loop.getEndPos(), loop.getTimes()); } ui->envEditor->setRelease(instADPCM->getEnvelopeRelease()); if (instADPCM->getEnvelopeEnabled()) { ui->envEditGroupBox->setChecked(true); onEnvelopeNumberChanged(); } else { ui->envEditGroupBox->setChecked(false); } } /********** Slots **********/ void AdpcmInstrumentEditor::onEnvelopeNumberChanged() { // Change users view std::multiset users = bt_.lock()->getEnvelopeADPCMUsers(ui->envNumSpinBox->value()); ui->envUsersLineEdit->setText(inst_edit_utils::generateUsersString(users)); } void AdpcmInstrumentEditor::onEnvelopeParameterChanged(int envNum) { if (ui->envNumSpinBox->value() == envNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentEnvelopeParameters(); } } void AdpcmInstrumentEditor::on_envEditGroupBox_toggled(bool arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentADPCMEnvelopeEnabled(instNum_, arg1); setInstrumentEnvelopeParameters(); emit envelopeNumberChanged(); emit modified(); } onEnvelopeNumberChanged(); } void AdpcmInstrumentEditor::on_envNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentADPCMEnvelope(instNum_, arg1); setInstrumentEnvelopeParameters(); emit envelopeNumberChanged(); emit modified(); } onEnvelopeNumberChanged(); } //--- Arpeggio void AdpcmInstrumentEditor::setInstrumentArpeggioParameters() { Ui::EventGuard ev(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instADPCM = dynamic_cast(inst.get()); ui->arpNumSpinBox->setValue(instADPCM->getArpeggioNumber()); ui->arpEditor->clearData(); for (auto& unit : instADPCM->getArpeggioSequence()) { ui->arpEditor->addSequenceData(unit.data); } for (auto& loop : instADPCM->getArpeggioLoopRoot().getAllLoops()) { ui->arpEditor->addLoop(loop.getBeginPos(), loop.getEndPos(), loop.getTimes()); } ui->arpEditor->setRelease(instADPCM->getArpeggioRelease()); for (int i = 0; i < ui->arpTypeComboBox->count(); ++i) { if (instADPCM->getArpeggioType() == static_cast(ui->arpTypeComboBox->itemData(i).toInt())) { ui->arpTypeComboBox->setCurrentIndex(i); break; } } if (instADPCM->getArpeggioEnabled()) { ui->arpEditGroupBox->setChecked(true); onArpeggioNumberChanged(); } else { ui->arpEditGroupBox->setChecked(false); } } /********** Slots **********/ void AdpcmInstrumentEditor::onArpeggioNumberChanged() { // Change users view std::multiset users = bt_.lock()->getArpeggioADPCMUsers(ui->arpNumSpinBox->value()); ui->arpUsersLineEdit->setText(inst_edit_utils::generateUsersString(users)); } void AdpcmInstrumentEditor::onArpeggioParameterChanged(int tnNum) { if (ui->arpNumSpinBox->value() == tnNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentArpeggioParameters(); } } void AdpcmInstrumentEditor::onArpeggioTypeChanged(int) { auto type = static_cast(ui->arpTypeComboBox->currentData(Qt::UserRole).toInt()); if (!isIgnoreEvent_) { bt_.lock()->setArpeggioADPCMType(ui->arpNumSpinBox->value(), type); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } ui->arpEditor->setSequenceType(type); } void AdpcmInstrumentEditor::on_arpEditGroupBox_toggled(bool arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentADPCMArpeggioEnabled(instNum_, arg1); setInstrumentArpeggioParameters(); emit arpeggioNumberChanged(); emit modified(); } onArpeggioNumberChanged(); } void AdpcmInstrumentEditor::on_arpNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentADPCMArpeggio(instNum_, arg1); setInstrumentArpeggioParameters(); emit arpeggioNumberChanged(); emit modified(); } onArpeggioNumberChanged(); } //--- Pitch void AdpcmInstrumentEditor::setInstrumentPitchParameters() { Ui::EventGuard ev(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instADPCM = dynamic_cast(inst.get()); ui->ptNumSpinBox->setValue(instADPCM->getPitchNumber()); ui->ptEditor->clearData(); for (auto& unit : instADPCM->getPitchSequence()) { ui->ptEditor->addSequenceData(unit.data); } for (auto& loop : instADPCM->getPitchLoopRoot().getAllLoops()) { ui->ptEditor->addLoop(loop.getBeginPos(), loop.getEndPos(), loop.getTimes()); } ui->ptEditor->setRelease(instADPCM->getPitchRelease()); for (int i = 0; i < ui->ptTypeComboBox->count(); ++i) { if (instADPCM->getPitchType() == static_cast(ui->ptTypeComboBox->itemData(i).toInt())) { ui->ptTypeComboBox->setCurrentIndex(i); break; } } if (instADPCM->getPitchEnabled()) { ui->ptEditGroupBox->setChecked(true); onPitchNumberChanged(); } else { ui->ptEditGroupBox->setChecked(false); } } /********** Slots **********/ void AdpcmInstrumentEditor::onPitchNumberChanged() { // Change users view std::multiset users = bt_.lock()->getPitchADPCMUsers(ui->ptNumSpinBox->value()); ui->ptUsersLineEdit->setText(inst_edit_utils::generateUsersString(users)); } void AdpcmInstrumentEditor::onPitchParameterChanged(int ptNum) { if (ui->ptNumSpinBox->value() == ptNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentPitchParameters(); } } void AdpcmInstrumentEditor::onPitchTypeChanged(int) { auto type = static_cast(ui->ptTypeComboBox->currentData(Qt::UserRole).toInt()); if (!isIgnoreEvent_) { bt_.lock()->setPitchADPCMType(ui->ptNumSpinBox->value(), type); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } ui->ptEditor->setSequenceType(type); } void AdpcmInstrumentEditor::on_ptEditGroupBox_toggled(bool arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentADPCMPitchEnabled(instNum_, arg1); setInstrumentPitchParameters(); emit pitchNumberChanged(); emit modified(); } onPitchNumberChanged(); } void AdpcmInstrumentEditor::on_ptNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentADPCMPitch(instNum_, arg1); setInstrumentPitchParameters(); emit pitchNumberChanged(); emit modified(); } onPitchNumberChanged(); } //--- Pan void AdpcmInstrumentEditor::setInstrumentPanParameters() { Ui::EventGuard ev(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instADPCM = dynamic_cast(inst.get()); ui->panNumSpinBox->setValue(instADPCM->getPanNumber()); ui->panEditor->clearData(); for (auto& unit : instADPCM->getPanSequence()) { ui->panEditor->addSequenceData(unit.data); } for (auto& loop : instADPCM->getPanLoopRoot().getAllLoops()) { ui->panEditor->addLoop(loop.getBeginPos(), loop.getEndPos(), loop.getTimes()); } ui->panEditor->setRelease(instADPCM->getPanRelease()); if (instADPCM->getPanEnabled()) { ui->panEditGroupBox->setChecked(true); onPanNumberChanged(); } else { ui->panEditGroupBox->setChecked(false); } } /********** Slots **********/ void AdpcmInstrumentEditor::onPanNumberChanged() { // Change users view std::multiset users = bt_.lock()->getPanADPCMUsers(ui->panNumSpinBox->value()); ui->panUsersLineEdit->setText(inst_edit_utils::generateUsersString(users)); } void AdpcmInstrumentEditor::onPanParameterChanged(int panNum) { if (ui->panNumSpinBox->value() == panNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentPanParameters(); } } void AdpcmInstrumentEditor::on_panEditGroupBox_toggled(bool arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentADPCMPanEnabled(instNum_, arg1); setInstrumentPanParameters(); emit panNumberChanged(); emit modified(); } onPanNumberChanged(); } void AdpcmInstrumentEditor::on_panNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentADPCMPan(instNum_, arg1); setInstrumentPanParameters(); emit panNumberChanged(); emit modified(); } onPanNumberChanged(); } BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/adpcm_instrument_editor.hpp000066400000000000000000000102401476276175200307630ustar00rootroot00000000000000/* * Copyright (C) 2020-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include "gui/instrument_editor/instrument_editor.hpp" #include "gui/instrument_editor/visualized_instrument_macro_editor.hpp" namespace Ui { class AdpcmInstrumentEditor; } class AdpcmInstrumentEditor final : public InstrumentEditor { Q_OBJECT public: explicit AdpcmInstrumentEditor(int num, QWidget* parent = nullptr); ~AdpcmInstrumentEditor() override; /** * @brief Return sound source of instrument related to this dialog. * @return ADPCM. */ SoundSource getSoundSource() const override; /** * @brief Return instrument type of instrument related to this dialog. * @return ADPCM. */ InstrumentType getInstrumentType() const override; void updateByConfigurationChange() override; protected: void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; private: Ui::AdpcmInstrumentEditor *ui; bool isIgnoreEvent_; // ADPCM specific process called in settiing core / cofiguration / color palette. void updateBySettingCore() override; void updateBySettingConfiguration() override; void updateBySettingColorPalette() override; void updateInstrumentParameters(); //========== Sample ==========// signals: void sampleNumberChanged(); void sampleParameterChanged(int sampNum, int fromInstNum); void sampleAssignRequested(); void sampleMemoryChanged(); public slots: void onSampleNumberChanged(); void onSampleParameterChanged(int sampNum); void onSampleMemoryUpdated(); private: void setInstrumentSampleParameters(); //========== Envelope ==========// signals: void envelopeNumberChanged(); void envelopeParameterChanged(int envNum, int fromInstNum); public slots: void onEnvelopeNumberChanged(); void onEnvelopeParameterChanged(int envNum); private: void setInstrumentEnvelopeParameters(); private slots: void on_envEditGroupBox_toggled(bool arg1); void on_envNumSpinBox_valueChanged(int arg1); //========== Arpeggio ==========// signals: void arpeggioNumberChanged(); void arpeggioParameterChanged(int arpNum, int fromInstNum); public slots: void onArpeggioNumberChanged(); void onArpeggioParameterChanged(int arpNum); private: void setInstrumentArpeggioParameters(); private slots: void onArpeggioTypeChanged(int); void on_arpEditGroupBox_toggled(bool arg1); void on_arpNumSpinBox_valueChanged(int arg1); //========== Pitch ==========// signals: void pitchNumberChanged(); void pitchParameterChanged(int ptNum, int fromInstNum); public slots: void onPitchNumberChanged(); void onPitchParameterChanged(int ptNum); private: void setInstrumentPitchParameters(); private slots: void onPitchTypeChanged(int); void on_ptEditGroupBox_toggled(bool arg1); void on_ptNumSpinBox_valueChanged(int arg1); //========== Pan ==========// signals: void panNumberChanged(); void panParameterChanged(int panNum, int fromInstNum); public slots: void onPanNumberChanged(); void onPanParameterChanged(int arpNum); private: void setInstrumentPanParameters(); private slots: void on_panEditGroupBox_toggled(bool arg1); void on_panNumSpinBox_valueChanged(int arg1); }; BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/adpcm_instrument_editor.ui000066400000000000000000000275521476276175200306270ustar00rootroot00000000000000 AdpcmInstrumentEditor 0 0 507 430 Form 0 false Sample Sample Envelope Envelope true false false # 127 Users: Qt::NoFocus true Qt::Horizontal 40 20 Arpeggio Arpeggio true false Type: false # 127 Qt::Horizontal 40 20 Users: Qt::NoFocus true Pitch Pitch true false Users: Type: Qt::Horizontal 40 20 # 127 Qt::NoFocus true Panning Panning true false false # 127 Users: Qt::NoFocus true Qt::Horizontal 40 20 VisualizedInstrumentMacroEditor QWidget
gui/instrument_editor/visualized_instrument_macro_editor.hpp
1
ArpeggioMacroEditor QWidget
gui/instrument_editor/arpeggio_macro_editor.hpp
1
PanMacroEditor QWidget
gui/instrument_editor/pan_macro_editor.hpp
1
ADPCMSampleEditor QWidget
gui/instrument_editor/adpcm_sample_editor.hpp
1
tabWidget envEditGroupBox envNumSpinBox arpEditGroupBox arpNumSpinBox arpTypeComboBox ptEditGroupBox ptNumSpinBox ptTypeComboBox panEditGroupBox panNumSpinBox
BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/adpcm_sample_editor.cpp000066400000000000000000000542271476276175200300440ustar00rootroot00000000000000/* * Copyright (C) 2020-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "adpcm_sample_editor.hpp" #include "ui_adpcm_sample_editor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "chip/codec/ymb_codec.hpp" #include "instrument/sample_adpcm.hpp" #include "gui/event_guard.hpp" #include "gui/instrument_editor/sample_length_dialog.hpp" #include "gui/instrument_editor/grid_settings_dialog.hpp" #include "gui/file_io_error_message_box.hpp" #include "gui/instrument_editor/instrument_editor_utils.hpp" #include "gui/note_name_manager.hpp" #include "utils.hpp" ADPCMSampleEditor::ADPCMSampleEditor(QWidget *parent) : QWidget(parent), ui(new Ui::ADPCMSampleEditor), isIgnoreEvent_(false), zoom_(0), viewedSampLen_(2), gridIntr_(1), cursorSamp_(0, 0), prevPressedSamp_(-1, -1), addrStart_(0), addrStop_(0), sample_(2), drawMode_(DrawMode::Disabled) { ui->setupUi(this); for (int i = 0; i < 12; ++i) { ui->rootKeyComboBox->addItem(NoteNameManager::getManager().getNoteName(i)); } auto rkfunc = [&](int) { if (!isIgnoreEvent_) { int rk = ui->rootKeySpinBox->value() * 12 + ui->rootKeyComboBox->currentIndex(); bt_.lock()->setSampleADPCMRootKeyNumber(ui->sampleNumSpinBox->value(), rk); emit sampleParameterChanged(ui->sampleNumSpinBox->value()); emit modified(); } }; // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->rootKeyComboBox, static_cast(&QComboBox::currentIndexChanged), this, rkfunc); QObject::connect(ui->rootKeySpinBox, static_cast(&QSpinBox::valueChanged), this, rkfunc); ui->memoryWidget->installEventFilter(this); ui->sampleViewWidget->setAttribute(Qt::WA_Hover); ui->sampleViewWidget->installEventFilter(this); auto tb = new QToolBar(); tb->setIconSize(QSize(16, 16)); tb->setMinimumHeight(28); tb->setMaximumHeight(28); tb->addActions({ ui->action_Clear, ui->action_Import, ui->action_Resize }); tb->addSeparator(); tb->addActions({ ui->actionZoom_In, ui->actionZoom_Out }); auto gridMenu = new QMenu(); gridMenu->addActions({ ui->action_Grid_View, ui->actionG_rid_Settings }); auto gridButton = new QToolButton(); gridButton->setPopupMode(QToolButton::MenuButtonPopup); gridButton->setMenu(gridMenu); gridButton->setDefaultAction(ui->action_Grid_View); tb->addWidget(gridButton); tb->addSeparator(); auto editMenu = new QMenu(); editMenu->addActions({ ui->action_Draw_Sample, ui->actionDirec_t_Draw }); auto editButton = new QToolButton(); editButton->setPopupMode(QToolButton::MenuButtonPopup); editButton->setMenu(editMenu); editButton->setDefaultAction(ui->action_Draw_Sample); QObject::connect(editButton, &QToolButton::triggered, editButton, &QToolButton::setDefaultAction); tb->addWidget(editButton); tb->addAction(ui->actionRe_verse); ui->verticalLayout_2->insertWidget(0, tb); } ADPCMSampleEditor::~ADPCMSampleEditor() { delete ui; } void ADPCMSampleEditor::setCore(std::weak_ptr core) { bt_ = core; } void ADPCMSampleEditor::setConfiguration(std::weak_ptr config) { config_ = config; } void ADPCMSampleEditor::setColorPalette(std::shared_ptr palette) { palette_ = palette; } int ADPCMSampleEditor::getSampleNumber() const { return ui->sampleNumSpinBox->value(); } void ADPCMSampleEditor::dragEnterEvent(QDragEnterEvent* event) { const QMimeData* mime = event->mimeData(); if (mime->hasUrls() && mime->urls().length() == 1 && QFileInfo(mime->urls().at(0).toLocalFile()).suffix().toLower() == "wav") { event->acceptProposedAction(); } } bool ADPCMSampleEditor::eventFilter(QObject* obj, QEvent* ev) { if (obj == ui->memoryWidget) { switch (ev->type()) { case QEvent::Paint: { QPainter painter(ui->memoryWidget); painter.drawPixmap(ui->memoryWidget->rect(), memPixmap_); break; } case QEvent::Resize: memPixmap_ = QPixmap(ui->memoryWidget->size()); updateSampleMemoryBar(); break; default: break; } return false; } if (obj == ui->sampleViewWidget) { switch (ev->type()) { case QEvent::Paint: { QPainter painter(ui->sampleViewWidget); painter.drawPixmap(ui->sampleViewWidget->rect(), sampViewPixmap_); break; } case QEvent::Resize: sampViewPixmap_ = QPixmap(ui->sampleViewWidget->size()); updateSampleView(); break; case QEvent::Wheel: { auto we = dynamic_cast(ev); int cnt = we->angleDelta().y() / 120; bool ctrl = we->modifiers().testFlag(Qt::ControlModifier); if (cnt > 0) { for (int i = 0; i < cnt; ++i) { if (ctrl) on_actionZoom_In_triggered(); else ui->horizontalScrollBar->setValue(ui->horizontalScrollBar->value() - 1); } } else { for (int i = 0; cnt < i; --i) { if (ctrl) on_actionZoom_Out_triggered(); else ui->horizontalScrollBar->setValue(ui->horizontalScrollBar->value() + 1); } } break; } case QEvent::HoverMove: { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) auto pos = dynamic_cast(ev)->position(); #else auto pos = dynamic_cast(ev)->pos(); #endif detectCursorSamplePosition(pos.x(), pos.y()); if (prevPressedSamp_.x() != -1) { // Change sample const int px = prevPressedSamp_.x(); const int py = prevPressedSamp_.y(); const int cx = cursorSamp_.x(); const int cy = cursorSamp_.y(); if (px < cx) { const int dx = cx - px; const int dy = cy - py; for (int x = px + 1; x <= cx; ++x) sample_.at(x) = (x - px) * dy / dx + py; } else if (px == cx) { sample_.at(cx) = cy; } else { const int dx = px - cx; const int dy = py - cy; for (int x = cx; x < px; ++x) sample_.at(x) = (x - cx) * dy / dx + cy; } prevPressedSamp_ = cursorSamp_; if (drawMode_ == DrawMode::Direct) { sendEditedSample(); } else { updateSampleView(); ui->sampleViewWidget->update(); } } break; } case QEvent::MouseButtonPress: { if (drawMode_ == DrawMode::Disabled) break; sample_.at(cursorSamp_.x()) = cursorSamp_.y(); prevPressedSamp_ = cursorSamp_; if (drawMode_ == DrawMode::Direct) { sendEditedSample(); } else { updateSampleView(); ui->sampleViewWidget->update(); } break; } case QEvent::MouseButtonRelease: { prevPressedSamp_.setX(-1); // Clear break; } default: break; } return false; } return false; } void ADPCMSampleEditor::dropEvent(QDropEvent* event) { importSampleFrom(reinterpret_cast(event)->mimeData()->urls().first().toLocalFile()); } void ADPCMSampleEditor::setInstrumentSampleParameters(int sampNum, bool repeatable, const SampleRepeatRange& repeatRange, int rKeyNum, int rDeltaN, size_t start, size_t stop, std::vector sample) { Ui::EventGuard ev(isIgnoreEvent_); ui->sampleNumSpinBox->setValue(sampNum); updateUsersView(); ui->repeatCheckBox->setChecked(repeatable); ui->repeatEndSpinBox->setMaximumByBytes(sample.size() - 1); ui->repeatBeginSpinBox->setMaximumByBytes(sample.size() - 1); ui->repeatEndSpinBox->setValueByAddress(repeatRange.last()); ui->repeatBeginSpinBox->setValueByAddress(repeatRange.first()); ui->rootKeyComboBox->setCurrentIndex(rKeyNum % 12); ui->rootKeySpinBox->setValue(rKeyNum / 12); ui->rootRateSpinBox->setValue(rDeltaN); addrStart_ = start; addrStop_ = stop; updateSampleMemoryBar(); ui->memoryWidget->update(); if (!ui->action_Draw_Sample->isChecked()) { size_t sampSize = sample.size() << 1; sample_.resize(sampSize); codec::ymb_decode(sample.data(), sample_.data(), static_cast(sampSize)); // Slider settings for (int z = 0, len = sampSize; ; ++z) { int tmp = (len + 1) >> 1; if (tmp < 2) { zoom_ = z; viewedSampLen_ = len; ui->horizontalScrollBar->setMaximum(sampSize - len); break; } else if (z == zoom_) { viewedSampLen_ = len; ui->horizontalScrollBar->setMaximum(sampSize - len); break; } else { len = tmp; } } updateSampleView(); ui->sampleViewWidget->update(); } ui->detailLabel->setText(updateDetailView()); } void ADPCMSampleEditor::importSampleFrom(const QString file) { std::unique_ptr wav; try { io::BinaryContainer container; { QFile fp(file); if (!fp.open(QIODevice::ReadOnly)) { FileIOErrorMessageBox::openError(file, true, io::FileType::WAV, this); return; } QByteArray array = fp.readAll(); fp.close(); std::move(array.begin(), array.end(), std::back_inserter(container)); } wav = std::make_unique(container); } catch (io::FileIOError& e) { FileIOErrorMessageBox(file, true, e, this).exec(); return; } catch (std::exception& e) { FileIOErrorMessageBox(file, true, io::FileType::WAV, QString(e.what()), this).exec(); return; } if (wav->getSampleRate() < 2000 || 55466 < wav->getSampleRate()) { FileIOErrorMessageBox(file, true, io::FileType::WAV, tr("Supported sample rate is 2kHz-55.5kHz, but the rate of selected sample is %1.") .arg(wav->getSampleRate()), this).exec(); return; } if (wav->getChannelCount() != 1) { FileIOErrorMessageBox(file, true, io::FileType::WAV, tr("The selected sample is not mono channel."), this).exec(); return; } io::BinaryContainer bc = wav->getSample(); size_t rawSize = bc.size() / 2; std::vector raw(rawSize); for (size_t i = 0; i < rawSize; ++i) { raw[i] = bc.readInt16(i * 2); } std::vector adpcm((raw.size() + 1) / 2); codec::ymb_encode(raw.data(), adpcm.data(), static_cast(raw.size())); bt_.lock()->storeSampleADPCMRawSample(ui->sampleNumSpinBox->value(), adpcm); ui->repeatCheckBox->setChecked(false); ui->repeatBeginSpinBox->setMaximumByBytes(adpcm.size() - 1); ui->repeatEndSpinBox->setMaximumByBytes(adpcm.size() - 1); ui->repeatBeginSpinBox->setValueByBytes(0); ui->repeatEndSpinBox->setValueByBytes(adpcm.size() - 1); ui->rootKeyComboBox->setCurrentIndex(SampleADPCM::DEF_ROOT_KEY % 12); ui->rootKeySpinBox->setValue(SampleADPCM::DEF_ROOT_KEY / 12); ui->rootRateSpinBox->setValue(SampleADPCM::calculateADPCMDeltaN(wav->getSampleRate())); emit modified(); emit sampleAssignRequested(); emit sampleParameterChanged(ui->sampleNumSpinBox->value()); } void ADPCMSampleEditor::updateSampleMemoryBar() { QRect bar = memPixmap_.rect(); if (!bar.isValid()) return; QPainter painter(&memPixmap_); painter.fillRect(bar, palette_->instADPCMMemBackColor); double maxSize = bt_.lock()->getADPCMStoredSize() >> 5; double limit = bt_.lock()->getADPCMLimit() >> 5; // By 32 bytes QRectF allSamp(0, 0, std::max(1., bar.width() * (maxSize / limit)), rect().height()); painter.fillRect(allSamp, palette_->instADPCMMemAllColor); if (addrStart_ || addrStart_ != addrStop_) { QRectF curSamp(bar.width() * (addrStart_ / limit), 0, std::max(1., bar.width() * ((addrStop_ - addrStart_) / limit)), rect().height()); painter.fillRect(curSamp, palette_->instADPCMMemCurColor); } } void ADPCMSampleEditor::updateSampleView() { QRect rect = sampViewPixmap_.rect(); if (!rect.isValid()) return; QPainter painter(&sampViewPixmap_); painter.fillRect(rect, palette_->instADPCMSampViewBackColor); painter.setPen(palette_->instADPCMSampViewCenterColor); const int maxX = rect.width(); const int centerY = rect.height() >> 1; painter.drawLine(0, centerY, maxX, centerY); QColor foreColor; switch (drawMode_) { case DrawMode::Disabled: foreColor = palette_->instADPCMSampViewForeColor; break; case DrawMode::Normal: foreColor = palette_->instADPCMSampViewDrawColor; break; case DrawMode::Direct: foreColor = palette_->instADPCMSampViewDirectDrawColor; break; } painter.setPen(foreColor); const int16_t maxY = std::numeric_limits::max(); const size_t first = ui->horizontalScrollBar->value(); const bool showGrid = ui->action_Grid_View->isChecked(); const bool showRepeat = ui->repeatCheckBox->isChecked(); size_t repeatBegin = static_cast(ui->repeatBeginSpinBox->value()); size_t repeatEnd = static_cast(ui->repeatEndSpinBox->value()); if (maxX < viewedSampLen_) { int prevY = centerY; size_t g = first; size_t prevp = -(viewedSampLen_ - 1) / (maxX - 1); for (int x = 0; x < maxX; ++x) { size_t i = (viewedSampLen_ - 1) * x / (maxX - 1); size_t p = i + first; int16_t sample = sample_.at(p); int y = centerY - (centerY * sample / maxY); if (showGrid && g <= p) { painter.setPen(palette_->instADPCMSampViewGridColor); painter.drawLine(x, 0, x, rect.height()); g = (g / gridIntr_ + 1) * gridIntr_; painter.setPen(foreColor); } if (showRepeat) { if (prevp < repeatBegin && repeatBegin <= p) { painter.setPen(palette_->instADPCMSampViewRepeatBeginColor); painter.drawLine(x, 0, x, rect.height()); painter.setPen(foreColor); } if (prevp < repeatEnd && repeatEnd <= p) { painter.setPen(palette_->instADPCMSampViewRepeatEndColor); painter.drawLine(x, 0, x, rect.height()); painter.setPen(foreColor); } } if (x) painter.drawLine(x - 1, prevY, x, y); prevY = y; prevp = p; } } else { QPoint prev, p; for (int i = 0; i < viewedSampLen_; ++i) { p.setX((maxX - 1) * i / (viewedSampLen_ - 1)); size_t sampP = i + first; int16_t sample = sample_[sampP]; p.setY(centerY - (centerY * sample / maxY)); if (showGrid && !(i % gridIntr_)) { painter.setPen(palette_->instADPCMSampViewGridColor); painter.drawLine(p.x(), 0, p.x(), rect.height()); painter.setPen(foreColor); } if (showRepeat) { if (sampP == repeatBegin) { painter.setPen(palette_->instADPCMSampViewRepeatBeginColor); painter.drawLine(p.x(), 0, p.x(), rect.height()); painter.setPen(foreColor); } if (sampP == repeatEnd) { painter.setPen(palette_->instADPCMSampViewRepeatEndColor); painter.drawLine(p.x(), 0, p.x(), rect.height()); painter.setPen(foreColor); } } if (p.x()) painter.drawLine(prev, p); prev = p; } } } void ADPCMSampleEditor::updateUsersView() { std::multiset users = bt_.lock()->getSampleADPCMUsers(ui->sampleNumSpinBox->value()); ui->usersLineEdit->setText(inst_edit_utils::generateUsersString(users)); } void ADPCMSampleEditor::detectCursorSamplePosition(int cx, int cy) { const QRect& rect = ui->sampleViewWidget->rect(); // Detect x const int w = rect.width(); if (viewedSampLen_ < w) { const double segW = rect.width() / (viewedSampLen_ - 1.); double th = segW / 2.; for (int i = 0; i < viewedSampLen_; ++i, th += segW) { if (cx < th) { cursorSamp_.setX(ui->horizontalScrollBar->value() + i); break; } } } else { cursorSamp_.setX( ui->horizontalScrollBar->value() + (viewedSampLen_ - 1) * utils::clamp(cx, 0, w - 1) / (w - 1)); } // Detect y const double centerY = rect.height() >> 1; int y = std::numeric_limits::max() * (centerY - cy) / centerY; cursorSamp_.setY(utils::clamp(y, static_cast(std::numeric_limits::min()), static_cast(std::numeric_limits::max()))); // Update position view ui->detailLabel->setText(updateDetailView()); } void ADPCMSampleEditor::sendEditedSample() { std::vector adpcm(sample_.size() >> 1); codec::ymb_encode(sample_.data(), adpcm.data(), static_cast(sample_.size())); bt_.lock()->storeSampleADPCMRawSample(ui->sampleNumSpinBox->value(), std::move(adpcm)); emit modified(); emit sampleAssignRequested(); emit sampleParameterChanged(ui->sampleNumSpinBox->value()); } void ADPCMSampleEditor::onSampleNumberChanged() { updateUsersView(); } void ADPCMSampleEditor::onSampleMemoryUpdated(size_t start, size_t stop) { addrStart_ = start; addrStop_ = stop; updateSampleMemoryBar(); ui->memoryWidget->update(); } void ADPCMSampleEditor::onNoteNamesUpdated() { for (int i = 0; i < 12; ++i) { ui->rootKeyComboBox->setItemText(i, NoteNameManager::getManager().getNoteName(i)); } } void ADPCMSampleEditor::on_sampleNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) emit sampleNumberChanged(arg1); // Direct connection onSampleNumberChanged(); updateSampleMemoryBar(); ui->memoryWidget->update(); updateSampleView(); ui->sampleViewWidget->update(); } void ADPCMSampleEditor::on_repeatCheckBox_toggled(bool checked) { ui->repeatBeginSpinBox->setEnabled(checked); ui->repeatEndSpinBox->setEnabled(checked); if (!isIgnoreEvent_) { bt_.lock()->setSampleADPCMRepeatEnabled(ui->sampleNumSpinBox->value(), checked); emit sampleParameterChanged(ui->sampleNumSpinBox->value()); emit modified(); } } void ADPCMSampleEditor::on_repeatBeginSpinBox_valueChanged(int) { if (isIgnoreEvent_) return; int begin = ui->repeatBeginSpinBox->valueByAddress(); if (ui->repeatEndSpinBox->valueByAddress() < begin) { ui->repeatEndSpinBox->setValueByAddress(begin); } bt_.lock()->setSampleADPCMRepeatRange(ui->sampleNumSpinBox->value(), SampleRepeatRange(begin, ui->repeatEndSpinBox->valueByAddress())); updateSampleView(); ui->sampleViewWidget->update(); } void ADPCMSampleEditor::on_repeatEndSpinBox_valueChanged(int) { if (isIgnoreEvent_) return; int end = ui->repeatEndSpinBox->valueByAddress(); if (end < ui->repeatBeginSpinBox->valueByAddress()) { ui->repeatBeginSpinBox->setValueByAddress(end); } bt_.lock()->setSampleADPCMRepeatRange(ui->sampleNumSpinBox->value(), SampleRepeatRange(ui->repeatBeginSpinBox->valueByAddress(), end)); updateSampleView(); ui->sampleViewWidget->update(); } void ADPCMSampleEditor::on_rootRateSpinBox_valueChanged(int arg1) { ui->rootRateSpinBox->setSuffix( QString(" (0x") + QString("%1 | ").arg(arg1, 3, 16, QChar('0')).toUpper() + QString("%1Hz)").arg(QString::number(arg1 * 55500. * std::pow(2., -16.), 'f', 3))); if (!isIgnoreEvent_) { bt_.lock()->setSampleADPCMRootDeltaN(ui->sampleNumSpinBox->value(), arg1); emit sampleParameterChanged(ui->sampleNumSpinBox->value()); emit modified(); } } void ADPCMSampleEditor::on_action_Resize_triggered() { SampleLengthDialog dialog(sample_.size(), this); if (dialog.exec() == QDialog::Accepted) { ui->repeatBeginSpinBox->setMaximumByBytes((dialog.getLength() - 1) >> 1); ui->repeatEndSpinBox->setMaximumByBytes((dialog.getLength() - 1) >> 1); sample_.resize(dialog.getLength()); sendEditedSample(); updateSampleView(); ui->sampleViewWidget->update(); } } void ADPCMSampleEditor::on_actionRe_verse_triggered() { std::reverse(sample_.begin(), sample_.end()); sendEditedSample(); updateSampleView(); ui->sampleViewWidget->update(); } void ADPCMSampleEditor::on_actionZoom_In_triggered() { int len = (viewedSampLen_ + 1) >> 1; if (len > 1) { ++zoom_; viewedSampLen_ = len; ui->horizontalScrollBar->setMaximum(sample_.size() - len); updateSampleView(); ui->sampleViewWidget->update(); ui->detailLabel->setText(updateDetailView()); } } void ADPCMSampleEditor::on_actionZoom_Out_triggered() { if (zoom_) { --zoom_; viewedSampLen_ = sample_.size() >> zoom_; ui->horizontalScrollBar->setMaximum(sample_.size() - viewedSampLen_); updateSampleView(); ui->sampleViewWidget->update(); ui->detailLabel->setText(updateDetailView()); } } void ADPCMSampleEditor::on_horizontalScrollBar_valueChanged(int) { updateSampleView(); ui->sampleViewWidget->update(); } void ADPCMSampleEditor::on_action_Import_triggered() { QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory()); QString file = QFileDialog::getOpenFileName(this, tr("Import sample"), (dir.isEmpty() ? "./" : dir), tr("WAV signed 16-bit PCM (*.wav)") + ";;" + tr("All files (*)"), nullptr #if defined(Q_OS_LINUX) || (defined(Q_OS_BSD4) && !defined(Q_OS_DARWIN)) , QFileDialog::DontUseNativeDialog #endif ); if (file.isNull()) return; importSampleFrom(file); } void ADPCMSampleEditor::on_action_Clear_triggered() { bt_.lock()->clearSampleADPCMRawSample(ui->sampleNumSpinBox->value()); updateSampleMemoryBar(); ui->memoryWidget->update(); emit modified(); if (config_.lock()->getWriteOnlyUsedSamples()) { emit sampleAssignRequested(); } else { emit sampleMemoryChanged(); } emit sampleParameterChanged(ui->sampleNumSpinBox->value()); } void ADPCMSampleEditor::on_action_Grid_View_triggered() { updateSampleView(); ui->sampleViewWidget->update(); } void ADPCMSampleEditor::on_actionG_rid_Settings_triggered() { GridSettingsDialog dialog(gridIntr_, this); if (dialog.exec() == QDialog::Accepted) { gridIntr_ = dialog.getInterval(); updateSampleView(); ui->sampleViewWidget->update(); } } void ADPCMSampleEditor::on_action_Draw_Sample_triggered(bool checked) { if (checked) { ui->actionDirec_t_Draw->setChecked(false); drawMode_ = DrawMode::Normal; } else { if (drawMode_ == DrawMode::Normal) drawMode_ = DrawMode::Disabled; sendEditedSample(); // Convert sample } updateSampleView(); } void ADPCMSampleEditor::on_actionDirec_t_Draw_triggered(bool checked) { if (checked) { ui->action_Draw_Sample->setChecked(false); if (drawMode_ == DrawMode::Normal) sendEditedSample(); // Convert sample drawMode_ = DrawMode::Direct; } else { if (drawMode_ == DrawMode::Direct) drawMode_ = DrawMode::Disabled; } updateSampleView(); } BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/adpcm_sample_editor.hpp000066400000000000000000000100461476276175200300400ustar00rootroot00000000000000/* * Copyright (C) 2020-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef ADPCM_SAMPLE_EDITOR_HPP #define ADPCM_SAMPLE_EDITOR_HPP #include #include #include #include #include #include #include #include #include #include "bamboo_tracker.hpp" #include "configuration.hpp" #include "instrument/sample_repeat.hpp" #include "gui/color_palette.hpp" namespace Ui { class ADPCMSampleEditor; } class ADPCMSampleEditor : public QWidget { Q_OBJECT public: explicit ADPCMSampleEditor(QWidget *parent = nullptr); ~ADPCMSampleEditor() override; void setCore(std::weak_ptr core); void setConfiguration(std::weak_ptr config); void setColorPalette(std::shared_ptr palette); int getSampleNumber() const; void setInstrumentSampleParameters(int sampNum, bool repeatable, const SampleRepeatRange& repeatRange, int rKeyNum, int rDeltaN, size_t start, size_t stop, std::vector sample); signals: void modified(); void sampleNumberChanged(int n); // NEED Direct connection void sampleParameterChanged(int wfNum); void sampleAssignRequested(); void sampleMemoryChanged(); public slots: void onSampleNumberChanged(); void onSampleMemoryUpdated(size_t start, size_t stop); void onNoteNamesUpdated(); protected: bool eventFilter(QObject* obj, QEvent* ev) override; void dragEnterEvent(QDragEnterEvent* event) override; void dropEvent(QDropEvent* event) override; private: Ui::ADPCMSampleEditor *ui; bool isIgnoreEvent_; std::weak_ptr bt_; std::weak_ptr config_; std::shared_ptr palette_; QPixmap memPixmap_, sampViewPixmap_; int zoom_, viewedSampLen_, gridIntr_; QPoint cursorSamp_, prevPressedSamp_; size_t addrStart_, addrStop_; std::vector sample_; void importSampleFrom(const QString file); void updateSampleMemoryBar(); void updateSampleView(); void updateUsersView(); void detectCursorSamplePosition(int cx, int cy); void sendEditedSample(); inline QString updateDetailView() const { return QString("(%1, %2), %3, x%4") .arg(cursorSamp_.x()).arg(cursorSamp_.y()).arg(sample_.size()).arg(zoom_ + 1); } enum class DrawMode { Disabled, Normal, Direct }; DrawMode drawMode_; private slots: void on_sampleNumSpinBox_valueChanged(int arg1); void on_repeatCheckBox_toggled(bool checked); void on_repeatBeginSpinBox_valueChanged(int); void on_repeatEndSpinBox_valueChanged(int); void on_rootRateSpinBox_valueChanged(int arg1); void on_action_Resize_triggered(); void on_actionRe_verse_triggered(); void on_actionZoom_In_triggered(); void on_actionZoom_Out_triggered(); void on_horizontalScrollBar_valueChanged(int); void on_action_Import_triggered(); void on_action_Clear_triggered(); void on_action_Grid_View_triggered(); void on_actionG_rid_Settings_triggered(); void on_action_Draw_Sample_triggered(bool checked); void on_actionDirec_t_Draw_triggered(bool checked); }; #endif // ADPCM_SAMPLE_EDITOR_HPP BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/adpcm_sample_editor.ui000066400000000000000000000237761476276175200277040ustar00rootroot00000000000000 ADPCMSampleEditor 0 0 400 340 true 0 0 0 0 false # 127 Users: true Qt::Horizontal 40 20 Memory: 0 0 140 0 0 0 0 true 0 Qt::Horizontal Repeat false 70 0 false 70 0 Qt::Horizontal 40 20 150 0 QFrame::WinPanel QFrame::Sunken , x, Qt::AlignCenter Root key Key 7 5 Rate 1 65535 :/icon/settings:/icon/settings &Resize :/icon/reverse:/icon/reverse Re&verse :/icon/zoom_in:/icon/zoom_in Zoom I&n :/icon/zoom_out:/icon/zoom_out Zoom &Out :/icon/load_inst:/icon/load_inst &Import :/icon/duplicate_order:/icon/duplicate_order &Clear true :/icon/eye:/icon/eye &Grid View Grid &Settings... true :/icon/edit_inst:/icon/edit_inst &Draw Sample true :/icon/chart_edit:/icon/chart_edit Direc&t Draw AdpcmStartAddressSpinBox QSpinBox
gui/instrument_editor/adpcm_address_spin_box.hpp
AdpcmStopAddressSpinBox QSpinBox
gui/instrument_editor/adpcm_address_spin_box.hpp
sampleNumSpinBox usersLineEdit repeatCheckBox repeatBeginSpinBox repeatEndSpinBox rootKeyComboBox rootKeySpinBox rootRateSpinBox
BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/arpeggio_macro_editor.cpp000066400000000000000000000062171476276175200303710ustar00rootroot00000000000000/* * Copyright (C) 2019-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "arpeggio_macro_editor.hpp" #include #include "note.hpp" #include "gui/note_name_manager.hpp" ArpeggioMacroEditor::ArpeggioMacroEditor(QWidget* parent) : VisualizedInstrumentMacroEditor(parent) { constexpr int MAX_DISP_CNT = 15; setMaximumDisplayedRowCount(MAX_DISP_CNT); setDefaultRow(Note::DEFAULT_NOTE_NUM); setLabelDiaplayMode(true); for (int i = 0; i < Note::NOTE_NUMBER_RANGE; ++i) { AddRow(QString::asprintf("%+d", i - Note::DEFAULT_NOTE_NUM), false); } autoFitLabelWidth(); setUpperRow(Note::DEFAULT_NOTE_NUM + MAX_DISP_CNT / 2); setMMLDisplay0As(-Note::DEFAULT_NOTE_NUM); setSequenceType(SequenceType::AbsoluteSequence); } QString ArpeggioMacroEditor::convertSequenceDataUnitToMML(Column col) { if (type_ == SequenceType::FixedSequence) return NoteNameManager::getManager().getNoteString(col.row); else return VisualizedInstrumentMacroEditor::convertSequenceDataUnitToMML(col); } bool ArpeggioMacroEditor::interpretDataInMML(QString &text, int &cnt, std::vector &column) { if (type_ == SequenceType::FixedSequence) { NoteNameManager& nnm = NoteNameManager::getManager(); for (int i = 0; i < 12; ++i) { QString name = QString("%1").arg(nnm.getNoteName(i), -2, QChar('-')); QRegularExpressionMatch m = QRegularExpression("^" + name + "([0-7])").match(text); if (m.hasMatch()) { int oct = m.captured(1).toInt(); int d = 12 * oct + i; column.push_back({ d, -1, "" }); ++cnt; text.remove(QRegularExpression("^" + name + "[0-7]")); return true; } } return false; } else { return VisualizedInstrumentMacroEditor::interpretDataInMML(text, cnt, column); } } void ArpeggioMacroEditor::onNoteNamesUpdated() { updateLabels(); printMML(); } void ArpeggioMacroEditor::updateLabels() { if (type_ == SequenceType::FixedSequence) { for (int i = 0; i < Note::NOTE_NUMBER_RANGE; ++i) setLabel(i, NoteNameManager::getManager().getNoteString(i)); } else { for (int i = 0; i < Note::NOTE_NUMBER_RANGE; ++i) setLabel(i, QString::asprintf("%+d", i - Note::DEFAULT_NOTE_NUM)); } } BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/arpeggio_macro_editor.hpp000066400000000000000000000032671476276175200304000ustar00rootroot00000000000000/* * Copyright (C) 2019-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef ARPEGGIO_MACRO_EDITOR_HPP #define ARPEGGIO_MACRO_EDITOR_HPP #include "visualized_instrument_macro_editor.hpp" #include #include "instrument.hpp" class ArpeggioMacroEditor final : public VisualizedInstrumentMacroEditor { Q_OBJECT public: ArpeggioMacroEditor(QWidget *parent = nullptr); protected: QString convertSequenceDataUnitToMML(Column col) override; bool interpretDataInMML(QString &text, int &cnt, std::vector &column) override; public slots: void onNoteNamesUpdated(); private: void updateLabels() override; }; #endif // ARPEGGIO_MACRO_EDITOR_HPP BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/fm_instrument_editor.cpp000066400000000000000000002363221476276175200303070ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "fm_instrument_editor.hpp" #include "ui_fm_instrument_editor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gui/event_guard.hpp" #include "gui/jam_layout.hpp" #include "gui/instrument_editor/instrument_editor_utils.hpp" #include "gui/gui_utils.hpp" FmInstrumentEditor::FmInstrumentEditor(int num, QWidget* parent) : InstrumentEditor(num, parent), ui(new Ui::FmInstrumentEditor), isIgnoreEvent_(false) { ui->setupUi(this); /******************** Envelope editor ********************/ ui->envGroupBox->setContextMenuPolicy(Qt::CustomContextMenu); auto scene = new QGraphicsScene(ui->alGraphicsView); ui->alGraphicsView->setScene(scene); ui->alSlider->setText("AL"); ui->alSlider->setMaximum(7); QObject::connect(ui->alSlider, &LabeledHorizontalSlider::valueChanged, this, [&](int value) { if (!isIgnoreEvent_) { bt_.lock()->setEnvelopeFMParameter(ui->envNumSpinBox->value(), FMEnvelopeParameter::AL, value); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } paintAlgorithmDiagram(); resizeAlgorithmDiagram(); }); ui->fbSlider->setText("FB"); ui->fbSlider->setMaximum(7); QObject::connect(ui->fbSlider, &LabeledHorizontalSlider::valueChanged, this, [&](int value) { if (!isIgnoreEvent_) { bt_.lock()->setEnvelopeFMParameter(ui->envNumSpinBox->value(), FMEnvelopeParameter::FB, value); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); ui->op1Table->setOperatorNumber(0); QObject::connect(ui->op1Table, &FMOperatorTable::operatorEnableChanged, this, [&](bool enable) { if (!isIgnoreEvent_) { bt_.lock()->setEnvelopeFMOperatorEnable(ui->envNumSpinBox->value(), 0, enable); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->op1Table, &FMOperatorTable::operatorValueChanged, this, [&](Ui::FMOperatorParameter opParam, int value) { if (!isIgnoreEvent_) { FMEnvelopeParameter param; switch (opParam) { case Ui::FMOperatorParameter::AR: param = FMEnvelopeParameter::AR1; break; case Ui::FMOperatorParameter::DR: param = FMEnvelopeParameter::DR1; break; case Ui::FMOperatorParameter::SR: param = FMEnvelopeParameter::SR1; break; case Ui::FMOperatorParameter::RR: param = FMEnvelopeParameter::RR1; break; case Ui::FMOperatorParameter::SL: param = FMEnvelopeParameter::SL1; break; case Ui::FMOperatorParameter::TL: param = FMEnvelopeParameter::TL1; break; case Ui::FMOperatorParameter::KS: param = FMEnvelopeParameter::KS1; break; case Ui::FMOperatorParameter::ML: param = FMEnvelopeParameter::ML1; break; case Ui::FMOperatorParameter::DT: param = FMEnvelopeParameter::DT1; break; case Ui::FMOperatorParameter::SSGEG: param = FMEnvelopeParameter::SSGEG1; break; default: throw std::invalid_argument("Unexpected FMOperatorParameter."); } bt_.lock()->setEnvelopeFMParameter(ui->envNumSpinBox->value(), param, value); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->op1Table, &FMOperatorTable::copyEnvelopePressed, this, &FmInstrumentEditor::copyEnvelope); QObject::connect(ui->op1Table, &FMOperatorTable::pasteEnvelopePressed, this, &FmInstrumentEditor::pasteEnvelope); QObject::connect(ui->op1Table, &FMOperatorTable::copyOperatorPressed, this, &FmInstrumentEditor::copyOperator); QObject::connect(ui->op1Table, &FMOperatorTable::pasteOperatorPressed, this, &FmInstrumentEditor::pasteOperator); QObject::connect(ui->op1Table, &FMOperatorTable::pasteEnvelopeFromPressed, this, &FmInstrumentEditor::pasteEnvelopeFrom); ui->op2Table->setOperatorNumber(1); QObject::connect(ui->op2Table, &FMOperatorTable::operatorEnableChanged, this, [&](bool enable) { if (!isIgnoreEvent_) { bt_.lock()->setEnvelopeFMOperatorEnable(ui->envNumSpinBox->value(), 1, enable); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->op2Table, &FMOperatorTable::operatorValueChanged, this, [&](Ui::FMOperatorParameter opParam, int value) { if (!isIgnoreEvent_) { FMEnvelopeParameter param; switch (opParam) { case Ui::FMOperatorParameter::AR: param = FMEnvelopeParameter::AR2; break; case Ui::FMOperatorParameter::DR: param = FMEnvelopeParameter::DR2; break; case Ui::FMOperatorParameter::SR: param = FMEnvelopeParameter::SR2; break; case Ui::FMOperatorParameter::RR: param = FMEnvelopeParameter::RR2; break; case Ui::FMOperatorParameter::SL: param = FMEnvelopeParameter::SL2; break; case Ui::FMOperatorParameter::TL: param = FMEnvelopeParameter::TL2; break; case Ui::FMOperatorParameter::KS: param = FMEnvelopeParameter::KS2; break; case Ui::FMOperatorParameter::ML: param = FMEnvelopeParameter::ML2; break; case Ui::FMOperatorParameter::DT: param = FMEnvelopeParameter::DT2; break; case Ui::FMOperatorParameter::SSGEG: param = FMEnvelopeParameter::SSGEG2; break; default: throw std::invalid_argument("Unexpected FMOperatorParameter."); } bt_.lock()->setEnvelopeFMParameter(ui->envNumSpinBox->value(), param, value); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->op2Table, &FMOperatorTable::copyEnvelopePressed, this, &FmInstrumentEditor::copyEnvelope); QObject::connect(ui->op2Table, &FMOperatorTable::pasteEnvelopePressed, this, &FmInstrumentEditor::pasteEnvelope); QObject::connect(ui->op2Table, &FMOperatorTable::copyOperatorPressed, this, &FmInstrumentEditor::copyOperator); QObject::connect(ui->op2Table, &FMOperatorTable::pasteOperatorPressed, this, &FmInstrumentEditor::pasteOperator); QObject::connect(ui->op2Table, &FMOperatorTable::pasteEnvelopeFromPressed, this, &FmInstrumentEditor::pasteEnvelopeFrom); ui->op3Table->setOperatorNumber(2); QObject::connect(ui->op3Table, &FMOperatorTable::operatorEnableChanged, this, [&](bool enable) { if (!isIgnoreEvent_) { bt_.lock()->setEnvelopeFMOperatorEnable(ui->envNumSpinBox->value(), 2, enable); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->op3Table, &FMOperatorTable::operatorValueChanged, this, [&](Ui::FMOperatorParameter opParam, int value) { if (!isIgnoreEvent_) { FMEnvelopeParameter param; switch (opParam) { case Ui::FMOperatorParameter::AR: param = FMEnvelopeParameter::AR3; break; case Ui::FMOperatorParameter::DR: param = FMEnvelopeParameter::DR3; break; case Ui::FMOperatorParameter::SR: param = FMEnvelopeParameter::SR3; break; case Ui::FMOperatorParameter::RR: param = FMEnvelopeParameter::RR3; break; case Ui::FMOperatorParameter::SL: param = FMEnvelopeParameter::SL3; break; case Ui::FMOperatorParameter::TL: param = FMEnvelopeParameter::TL3; break; case Ui::FMOperatorParameter::KS: param = FMEnvelopeParameter::KS3; break; case Ui::FMOperatorParameter::ML: param = FMEnvelopeParameter::ML3; break; case Ui::FMOperatorParameter::DT: param = FMEnvelopeParameter::DT3; break; case Ui::FMOperatorParameter::SSGEG: param = FMEnvelopeParameter::SSGEG3; break; default: throw std::invalid_argument("Unexpected FMOperatorParameter."); } bt_.lock()->setEnvelopeFMParameter(ui->envNumSpinBox->value(), param, value); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->op3Table, &FMOperatorTable::copyEnvelopePressed, this, &FmInstrumentEditor::copyEnvelope); QObject::connect(ui->op3Table, &FMOperatorTable::pasteEnvelopePressed, this, &FmInstrumentEditor::pasteEnvelope); QObject::connect(ui->op3Table, &FMOperatorTable::copyOperatorPressed, this, &FmInstrumentEditor::copyOperator); QObject::connect(ui->op3Table, &FMOperatorTable::pasteOperatorPressed, this, &FmInstrumentEditor::pasteOperator); QObject::connect(ui->op3Table, &FMOperatorTable::pasteEnvelopeFromPressed, this, &FmInstrumentEditor::pasteEnvelopeFrom); ui->op4Table->setOperatorNumber(3); QObject::connect(ui->op4Table, &FMOperatorTable::operatorEnableChanged, this, [&](bool enable) { if (!isIgnoreEvent_) { bt_.lock()->setEnvelopeFMOperatorEnable(ui->envNumSpinBox->value(), 3, enable); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->op4Table, &FMOperatorTable::operatorValueChanged, this, [&](Ui::FMOperatorParameter opParam, int value) { if (!isIgnoreEvent_) { FMEnvelopeParameter param; switch (opParam) { case Ui::FMOperatorParameter::AR: param = FMEnvelopeParameter::AR4; break; case Ui::FMOperatorParameter::DR: param = FMEnvelopeParameter::DR4; break; case Ui::FMOperatorParameter::SR: param = FMEnvelopeParameter::SR4; break; case Ui::FMOperatorParameter::RR: param = FMEnvelopeParameter::RR4; break; case Ui::FMOperatorParameter::SL: param = FMEnvelopeParameter::SL4; break; case Ui::FMOperatorParameter::TL: param = FMEnvelopeParameter::TL4; break; case Ui::FMOperatorParameter::KS: param = FMEnvelopeParameter::KS4; break; case Ui::FMOperatorParameter::ML: param = FMEnvelopeParameter::ML4; break; case Ui::FMOperatorParameter::DT: param = FMEnvelopeParameter::DT4; break; case Ui::FMOperatorParameter::SSGEG: param = FMEnvelopeParameter::SSGEG4; break; default: throw std::invalid_argument("Unexpected FMOperatorParameter."); } bt_.lock()->setEnvelopeFMParameter(ui->envNumSpinBox->value(), param, value); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->op4Table, &FMOperatorTable::copyEnvelopePressed, this, &FmInstrumentEditor::copyEnvelope); QObject::connect(ui->op4Table, &FMOperatorTable::pasteEnvelopePressed, this, &FmInstrumentEditor::pasteEnvelope); QObject::connect(ui->op4Table, &FMOperatorTable::copyOperatorPressed, this, &FmInstrumentEditor::copyOperator); QObject::connect(ui->op4Table, &FMOperatorTable::pasteOperatorPressed, this, &FmInstrumentEditor::pasteOperator); QObject::connect(ui->op4Table, &FMOperatorTable::pasteEnvelopeFromPressed, this, &FmInstrumentEditor::pasteEnvelopeFrom); /******************** LFO editor ********************/ ui->lfoGroupBox->setContextMenuPolicy(Qt::CustomContextMenu); ui->lfoFreqSlider->setText(tr("Freq")); ui->lfoFreqSlider->setMaximum(7); QObject::connect(ui->lfoFreqSlider, &LabeledVerticalSlider::valueChanged, this, [&](int v) { if (!isIgnoreEvent_) { bt_.lock()->setLFOFMParameter(ui->lfoNumSpinBox->value(), FMLFOParameter::FREQ, v); emit lfoParameterChanged(ui->lfoNumSpinBox->value(), instNum_); emit modified(); } }); ui->pmsSlider->setText("PMS"); ui->pmsSlider->setMaximum(7); QObject::connect(ui->pmsSlider, &LabeledVerticalSlider::valueChanged, this, [&](int v) { if (!isIgnoreEvent_) { bt_.lock()->setLFOFMParameter(ui->lfoNumSpinBox->value(), FMLFOParameter::PMS, v); emit lfoParameterChanged(ui->lfoNumSpinBox->value(), instNum_); emit modified(); } }); ui->amsSlider->setText("AMS"); ui->amsSlider->setMaximum(3); QObject::connect(ui->amsSlider, &LabeledVerticalSlider::valueChanged, this, [&](int v) { if (!isIgnoreEvent_) { bt_.lock()->setLFOFMParameter(ui->lfoNumSpinBox->value(), FMLFOParameter::AMS, v); emit lfoParameterChanged(ui->lfoNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->amOp1CheckBox, &QCheckBox::stateChanged, this, [&](int state) { if (!isIgnoreEvent_) { bt_.lock()->setLFOFMParameter(ui->lfoNumSpinBox->value(), FMLFOParameter::AM1, (state == Qt::Checked) ? 1 : 0); emit lfoParameterChanged(ui->lfoNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->amOp2CheckBox, &QCheckBox::stateChanged, this, [&](int state) { if (!isIgnoreEvent_) { bt_.lock()->setLFOFMParameter(ui->lfoNumSpinBox->value(), FMLFOParameter::AM2, (state == Qt::Checked) ? 1 : 0); emit lfoParameterChanged(ui->lfoNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->amOp3CheckBox, &QCheckBox::stateChanged, this, [&](int state) { if (!isIgnoreEvent_) { bt_.lock()->setLFOFMParameter(ui->lfoNumSpinBox->value(), FMLFOParameter::AM3, (state == Qt::Checked) ? 1 : 0); emit lfoParameterChanged(ui->lfoNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->amOp4CheckBox, &QCheckBox::stateChanged, this, [&](int state) { if (!isIgnoreEvent_) { bt_.lock()->setLFOFMParameter(ui->lfoNumSpinBox->value(), FMLFOParameter::AM4, (state == Qt::Checked) ? 1 : 0); emit lfoParameterChanged(ui->lfoNumSpinBox->value(), instNum_); emit modified(); } }); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->lfoStartSpinBox, static_cast(&QSpinBox::valueChanged), this, [&](int v) { if (!isIgnoreEvent_) { bt_.lock()->setLFOFMParameter(ui->lfoNumSpinBox->value(), FMLFOParameter::Count, v); emit lfoParameterChanged(ui->lfoNumSpinBox->value(), instNum_); emit modified(); } }); //========== OperatorSequence ==========// ui->opSeqTypeComboBox->addItem("AL", static_cast(FMEnvelopeParameter::AL)); ui->opSeqTypeComboBox->addItem("FB", static_cast(FMEnvelopeParameter::FB)); ui->opSeqTypeComboBox->addItem("AR1", static_cast(FMEnvelopeParameter::AR1)); ui->opSeqTypeComboBox->addItem("DR1", static_cast(FMEnvelopeParameter::DR1)); ui->opSeqTypeComboBox->addItem("SR1", static_cast(FMEnvelopeParameter::SR1)); ui->opSeqTypeComboBox->addItem("RR1", static_cast(FMEnvelopeParameter::RR1)); ui->opSeqTypeComboBox->addItem("SL1", static_cast(FMEnvelopeParameter::SL1)); ui->opSeqTypeComboBox->addItem("TL1", static_cast(FMEnvelopeParameter::TL1)); ui->opSeqTypeComboBox->addItem("KS1", static_cast(FMEnvelopeParameter::KS1)); ui->opSeqTypeComboBox->addItem("ML1", static_cast(FMEnvelopeParameter::ML1)); ui->opSeqTypeComboBox->addItem("DT1", static_cast(FMEnvelopeParameter::DT1)); ui->opSeqTypeComboBox->addItem("AR2", static_cast(FMEnvelopeParameter::AR2)); ui->opSeqTypeComboBox->addItem("DR2", static_cast(FMEnvelopeParameter::DR2)); ui->opSeqTypeComboBox->addItem("SR2", static_cast(FMEnvelopeParameter::SR2)); ui->opSeqTypeComboBox->addItem("RR2", static_cast(FMEnvelopeParameter::RR2)); ui->opSeqTypeComboBox->addItem("SL2", static_cast(FMEnvelopeParameter::SL2)); ui->opSeqTypeComboBox->addItem("TL2", static_cast(FMEnvelopeParameter::TL2)); ui->opSeqTypeComboBox->addItem("KS2", static_cast(FMEnvelopeParameter::KS2)); ui->opSeqTypeComboBox->addItem("ML2", static_cast(FMEnvelopeParameter::ML2)); ui->opSeqTypeComboBox->addItem("DT2", static_cast(FMEnvelopeParameter::DT2)); ui->opSeqTypeComboBox->addItem("AR3", static_cast(FMEnvelopeParameter::AR3)); ui->opSeqTypeComboBox->addItem("DR3", static_cast(FMEnvelopeParameter::DR3)); ui->opSeqTypeComboBox->addItem("SR3", static_cast(FMEnvelopeParameter::SR3)); ui->opSeqTypeComboBox->addItem("RR3", static_cast(FMEnvelopeParameter::RR3)); ui->opSeqTypeComboBox->addItem("SL3", static_cast(FMEnvelopeParameter::SL3)); ui->opSeqTypeComboBox->addItem("TL3", static_cast(FMEnvelopeParameter::TL3)); ui->opSeqTypeComboBox->addItem("KS3", static_cast(FMEnvelopeParameter::KS3)); ui->opSeqTypeComboBox->addItem("ML3", static_cast(FMEnvelopeParameter::ML3)); ui->opSeqTypeComboBox->addItem("DT3", static_cast(FMEnvelopeParameter::DT3)); ui->opSeqTypeComboBox->addItem("AR4", static_cast(FMEnvelopeParameter::AR4)); ui->opSeqTypeComboBox->addItem("DR4", static_cast(FMEnvelopeParameter::DR4)); ui->opSeqTypeComboBox->addItem("SR4", static_cast(FMEnvelopeParameter::SR4)); ui->opSeqTypeComboBox->addItem("RR4", static_cast(FMEnvelopeParameter::RR4)); ui->opSeqTypeComboBox->addItem("SL4", static_cast(FMEnvelopeParameter::SL4)); ui->opSeqTypeComboBox->addItem("TL4", static_cast(FMEnvelopeParameter::TL4)); ui->opSeqTypeComboBox->addItem("KS4", static_cast(FMEnvelopeParameter::KS4)); ui->opSeqTypeComboBox->addItem("ML4", static_cast(FMEnvelopeParameter::ML4)); ui->opSeqTypeComboBox->addItem("DT4", static_cast(FMEnvelopeParameter::DT4)); ui->opSeqEditor->setDefaultRow(0); ui->opSeqEditor->setLabelDiaplayMode(true); setOperatorSequenceEditor(); QObject::connect(ui->opSeqEditor, &VisualizedInstrumentMacroEditor::sequenceDataAdded, this, [&](int row, int) { if (!isIgnoreEvent_) { FMEnvelopeParameter param = getOperatorSequenceParameter(); bt_.lock()->addOperatorSequenceFMSequenceData(param, ui->opSeqNumSpinBox->value(), row); emit operatorSequenceParameterChanged(param, ui->opSeqNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->opSeqEditor, &VisualizedInstrumentMacroEditor::sequenceDataRemoved, this, [&] { if (!isIgnoreEvent_) { FMEnvelopeParameter param = getOperatorSequenceParameter(); bt_.lock()->removeOperatorSequenceFMSequenceData(param, ui->opSeqNumSpinBox->value()); emit operatorSequenceParameterChanged(param, ui->opSeqNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->opSeqEditor, &VisualizedInstrumentMacroEditor::sequenceDataChanged, this, [&](int row, int col) { if (!isIgnoreEvent_) { FMEnvelopeParameter param = getOperatorSequenceParameter(); bt_.lock()->setOperatorSequenceFMSequenceData(param, ui->opSeqNumSpinBox->value(), col, row); emit operatorSequenceParameterChanged(param, ui->opSeqNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->opSeqEditor, &VisualizedInstrumentMacroEditor::loopAdded, this, [&](InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { FMEnvelopeParameter param = getOperatorSequenceParameter(); bt_.lock()->addOperatorSequenceFMLoop(param, ui->opSeqNumSpinBox->value(), loop); emit operatorSequenceParameterChanged(param, ui->opSeqNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->opSeqEditor, &VisualizedInstrumentMacroEditor::loopRemoved, this, [&](int begin, int end) { if (!isIgnoreEvent_) { FMEnvelopeParameter param = getOperatorSequenceParameter(); bt_.lock()->removeOperatorSequenceFMLoop( param, ui->opSeqNumSpinBox->value(), begin, end); emit operatorSequenceParameterChanged(param, ui->opSeqNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->opSeqEditor, &VisualizedInstrumentMacroEditor::loopCleared, this, [&] { if (!isIgnoreEvent_) { FMEnvelopeParameter param = getOperatorSequenceParameter(); bt_.lock()->clearOperatorSequenceFMLoops(param, ui->opSeqNumSpinBox->value()); emit operatorSequenceParameterChanged(param, ui->opSeqNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->opSeqEditor, &VisualizedInstrumentMacroEditor::loopChanged, this, [&](int prevBegin, int prevEnd, InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { FMEnvelopeParameter param = getOperatorSequenceParameter(); bt_.lock()->changeOperatorSequenceFMLoop( param, ui->opSeqNumSpinBox->value(), prevBegin, prevEnd, loop); emit operatorSequenceParameterChanged(param, ui->opSeqNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->opSeqEditor, &VisualizedInstrumentMacroEditor::releaseChanged, this, [&](InstrumentSequenceRelease release) { if (!isIgnoreEvent_) { FMEnvelopeParameter param = getOperatorSequenceParameter(); bt_.lock()->setOperatorSequenceFMRelease(param, ui->opSeqNumSpinBox->value(), release); emit operatorSequenceParameterChanged(param, ui->opSeqNumSpinBox->value(), instNum_); emit modified(); } }); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->opSeqTypeComboBox, static_cast(&QComboBox::currentIndexChanged), this, &FmInstrumentEditor::onOperatorSequenceTypeChanged); //========== Arpeggio ==========// ui->arpOpComboBox->addItem(tr("All"), static_cast(FMOperatorType::All)); ui->arpOpComboBox->addItem("Op1", static_cast(FMOperatorType::Op1)); ui->arpOpComboBox->addItem("Op2", static_cast(FMOperatorType::Op2)); ui->arpOpComboBox->addItem("Op3", static_cast(FMOperatorType::Op3)); ui->arpOpComboBox->addItem("Op4", static_cast(FMOperatorType::Op4)); ui->arpTypeComboBox->addItem(tr("Absolute"), static_cast(SequenceType::AbsoluteSequence)); ui->arpTypeComboBox->addItem(tr("Fixed"), static_cast(SequenceType::FixedSequence)); ui->arpTypeComboBox->addItem(tr("Relative"), static_cast(SequenceType::RelativeSequence)); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::sequenceDataAdded, this, [&](int row, int) { if (!isIgnoreEvent_) { bt_.lock()->addArpeggioFMSequenceData(ui->arpNumSpinBox->value(), row); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::sequenceDataRemoved, this, [&] { if (!isIgnoreEvent_) { bt_.lock()->removeArpeggioFMSequenceData(ui->arpNumSpinBox->value()); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::sequenceDataChanged, this, [&](int row, int col) { if (!isIgnoreEvent_) { bt_.lock()->setArpeggioFMSequenceData(ui->arpNumSpinBox->value(), col, row); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::loopAdded, this, [&](InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->addArpeggioFMLoop(ui->arpNumSpinBox->value(), loop); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::loopRemoved, this, [&](int begin, int end) { if (!isIgnoreEvent_) { bt_.lock()->removeArpeggioFMLoop(ui->arpNumSpinBox->value(), begin, end); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::loopCleared, this, [&] { if (!isIgnoreEvent_) { bt_.lock()->clearArpeggioFMLoops(ui->arpNumSpinBox->value()); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::loopChanged, this, [&](int prevBegin, int prevEnd, InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->changeArpeggioFMLoop(ui->arpNumSpinBox->value(), prevBegin, prevEnd, loop); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::releaseChanged, this, [&](InstrumentSequenceRelease release) { if (!isIgnoreEvent_) { bt_.lock()->setArpeggioFMRelease(ui->arpNumSpinBox->value(), release); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->arpTypeComboBox, static_cast(&QComboBox::currentIndexChanged), this, &FmInstrumentEditor::onArpeggioTypeChanged); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->arpOpComboBox, static_cast(&QComboBox::currentIndexChanged), this, &FmInstrumentEditor::onArpeggioOperatorChanged); //========== Pitch ==========// ui->ptOpComboBox->addItem(tr("All"), static_cast(FMOperatorType::All)); ui->ptOpComboBox->addItem("Op1", static_cast(FMOperatorType::Op1)); ui->ptOpComboBox->addItem("Op2", static_cast(FMOperatorType::Op2)); ui->ptOpComboBox->addItem("Op3", static_cast(FMOperatorType::Op3)); ui->ptOpComboBox->addItem("Op4", static_cast(FMOperatorType::Op4)); ui->ptEditor->setMaximumDisplayedRowCount(15); ui->ptEditor->setDefaultRow(127); ui->ptEditor->setLabelDiaplayMode(true); for (int i = 0; i < 255; ++i) { ui->ptEditor->AddRow(QString::asprintf("%+d", i - 127), false); } ui->ptEditor->autoFitLabelWidth(); ui->ptEditor->setUpperRow(134); ui->ptEditor->setMMLDisplay0As(-127); ui->ptTypeComboBox->addItem(tr("Absolute"), static_cast(SequenceType::AbsoluteSequence)); ui->ptTypeComboBox->addItem(tr("Relative"), static_cast(SequenceType::RelativeSequence)); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::sequenceDataAdded, this, [&](int row, int) { if (!isIgnoreEvent_) { bt_.lock()->addPitchFMSequenceData(ui->ptNumSpinBox->value(), row); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::sequenceDataRemoved, this, [&]() { if (!isIgnoreEvent_) { bt_.lock()->removePitchFMSequenceData(ui->ptNumSpinBox->value()); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::sequenceDataChanged, this, [&](int row, int col) { if (!isIgnoreEvent_) { bt_.lock()->setPitchFMSequenceData(ui->ptNumSpinBox->value(), col, row); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::loopAdded, this, [&](InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->addPitchFMLoop(ui->ptNumSpinBox->value(), loop); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::loopRemoved, this, [&](int begin, int end) { if (!isIgnoreEvent_) { bt_.lock()->removePitchFMLoop(ui->ptNumSpinBox->value(), begin, end); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::loopCleared, this, [&] { if (!isIgnoreEvent_) { bt_.lock()->clearPitchFMLoops(ui->ptNumSpinBox->value()); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::loopChanged, this, [&](int prevBegin, int prevEnd, InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->changePitchFMLoop(ui->ptNumSpinBox->value(), prevBegin, prevEnd, loop); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::releaseChanged, this, [&](InstrumentSequenceRelease release) { if (!isIgnoreEvent_) { bt_.lock()->setPitchFMRelease(ui->ptNumSpinBox->value(), release); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->ptTypeComboBox, static_cast(&QComboBox::currentIndexChanged), this, &FmInstrumentEditor::onPitchTypeChanged); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->ptOpComboBox, static_cast(&QComboBox::currentIndexChanged), this, &FmInstrumentEditor::onPitchOperatorChanged); //========== Pan ==========// QObject::connect(ui->panEditor, &VisualizedInstrumentMacroEditor::sequenceDataAdded, this, [&](int row, int) { if (!isIgnoreEvent_) { bt_.lock()->addPanFMSequenceData(ui->panNumSpinBox->value(), row); emit panParameterChanged(ui->panNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->panEditor, &VisualizedInstrumentMacroEditor::sequenceDataRemoved, this, [&] { if (!isIgnoreEvent_) { bt_.lock()->removePanFMSequenceData(ui->panNumSpinBox->value()); emit panParameterChanged(ui->panNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->panEditor, &VisualizedInstrumentMacroEditor::sequenceDataChanged, this, [&](int row, int col) { if (!isIgnoreEvent_) { bt_.lock()->setPanFMSequenceData(ui->panNumSpinBox->value(), col, row); emit panParameterChanged(ui->panNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->panEditor, &VisualizedInstrumentMacroEditor::loopAdded, this, [&](InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->addPanFMLoop(ui->panNumSpinBox->value(), loop); emit panParameterChanged(ui->panNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->panEditor, &VisualizedInstrumentMacroEditor::loopRemoved, this, [&](int begin, int end) { if (!isIgnoreEvent_) { bt_.lock()->removePanFMLoop(ui->panNumSpinBox->value(), begin, end); emit panParameterChanged(ui->panNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->panEditor, &VisualizedInstrumentMacroEditor::loopCleared, this, [&] { if (!isIgnoreEvent_) { bt_.lock()->clearPanFMLoops(ui->panNumSpinBox->value()); emit panParameterChanged(ui->panNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->panEditor, &VisualizedInstrumentMacroEditor::loopChanged, this, [&](int prevBegin, int prevEnd, InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->changePanFMLoop(ui->panNumSpinBox->value(), prevBegin, prevEnd, loop); emit panParameterChanged(ui->panNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->panEditor, &VisualizedInstrumentMacroEditor::releaseChanged, this, [&](InstrumentSequenceRelease release) { if (!isIgnoreEvent_) { bt_.lock()->setPanFMRelease(ui->panNumSpinBox->value(), release); emit panParameterChanged(ui->panNumSpinBox->value(), instNum_); emit modified(); } }); //========== Others ==========// QObject::connect(ui->envResetCheckBox, &QCheckBox::stateChanged, this, [&](int state) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMEnvelopeResetEnabled(instNum_, FMOperatorType::All, (state == Qt::Checked)); emit modified(); } }); QObject::connect(ui->envResetOp1CheckBox, &QCheckBox::stateChanged, this, [&](int state) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMEnvelopeResetEnabled(instNum_, FMOperatorType::Op1, (state == Qt::Checked)); emit modified(); } }); QObject::connect(ui->envResetOp2CheckBox, &QCheckBox::stateChanged, this, [&](int state) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMEnvelopeResetEnabled(instNum_, FMOperatorType::Op2, (state == Qt::Checked)); emit modified(); } }); QObject::connect(ui->envResetOp3CheckBox, &QCheckBox::stateChanged, this, [&](int state) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMEnvelopeResetEnabled(instNum_, FMOperatorType::Op3, (state == Qt::Checked)); emit modified(); } }); QObject::connect(ui->envResetOp4CheckBox, &QCheckBox::stateChanged, this, [&](int state) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMEnvelopeResetEnabled(instNum_, FMOperatorType::Op4, (state == Qt::Checked)); emit modified(); } }); } FmInstrumentEditor::~FmInstrumentEditor() { delete ui; } SoundSource FmInstrumentEditor::getSoundSource() const { return SoundSource::FM; } InstrumentType FmInstrumentEditor::getInstrumentType() const { return InstrumentType::FM; } void FmInstrumentEditor::updateByConfigurationChange() { ui->op1Table->setDTDisplayType(config_.lock()->getShowFMDetuneAsSigned()); ui->op2Table->setDTDisplayType(config_.lock()->getShowFMDetuneAsSigned()); ui->op3Table->setDTDisplayType(config_.lock()->getShowFMDetuneAsSigned()); ui->op4Table->setDTDisplayType(config_.lock()->getShowFMDetuneAsSigned()); ui->arpEditor->onNoteNamesUpdated(); } void FmInstrumentEditor::updateBySettingCore() { updateInstrumentParameters(); } void FmInstrumentEditor::updateBySettingConfiguration() { std::vector names; for (const auto& texts : config_.lock()->getFMEnvelopeTexts()) { names.push_back(gui_utils::utf8ToQString(texts.name)); } ui->op1Table->setEnvelopeSetNames(names); ui->op2Table->setEnvelopeSetNames(names); ui->op3Table->setEnvelopeSetNames(names); ui->op4Table->setEnvelopeSetNames(names); updateByConfigurationChange(); } void FmInstrumentEditor::updateBySettingColorPalette() { ui->op1Table->setColorPalette(palette_); ui->op2Table->setColorPalette(palette_); ui->op3Table->setColorPalette(palette_); ui->op4Table->setColorPalette(palette_); ui->opSeqEditor->setColorPalette(palette_); ui->arpEditor->setColorPalette(palette_); ui->ptEditor->setColorPalette(palette_); ui->panEditor->setColorPalette(palette_); } void FmInstrumentEditor::updateInstrumentParameters() { Ui::EventGuard eg(isIgnoreEvent_); updateWindowTitle(); setInstrumentEnvelopeParameters(); setInstrumentLFOParameters(); setInstrumentOperatorSequenceParameters(); setInstrumentArpeggioParameters(); setInstrumentPitchParameters(); setInstrumentPanParameters(); setInstrumentEnvelopeResetParameters(); } /********** Events **********/ // MUST DIRECT CONNECTION void FmInstrumentEditor::keyPressEvent(QKeyEvent *event) { // General keys switch (event->key()) { case Qt::Key_Escape: close(); break; default: // For jam key on if (!event->isAutoRepeat()) { // Musical keyboard Qt::Key qtKey = static_cast(event->key()); try { JamKey jk = getJamKeyFromLayoutMapping(qtKey, config_); emit jamKeyOnEvent(jk); } catch (std::invalid_argument&) {} } break; } } // MUST DIRECT CONNECTION void FmInstrumentEditor::keyReleaseEvent(QKeyEvent *event) { // For jam key off if (!event->isAutoRepeat()) { Qt::Key qtKey = static_cast(event->key()); try { JamKey jk = getJamKeyFromLayoutMapping(qtKey, config_); emit jamKeyOffEvent(jk); } catch (std::invalid_argument&) {} } } void FmInstrumentEditor::showEvent(QShowEvent*) { paintAlgorithmDiagram(); resizeAlgorithmDiagram(); } void FmInstrumentEditor::resizeEvent(QResizeEvent*) { resizeAlgorithmDiagram(); } //========== Envelope ==========// void FmInstrumentEditor::setInstrumentEnvelopeParameters() { Ui::EventGuard eg(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instFM = dynamic_cast(inst.get()); ui->envNumSpinBox->setValue(instFM->getEnvelopeNumber()); onEnvelopeNumberChanged(); ui->alSlider->setValue(instFM->getEnvelopeParameter(FMEnvelopeParameter::AL)); ui->fbSlider->setValue(instFM->getEnvelopeParameter(FMEnvelopeParameter::FB)); ui->op1Table->setValue(Ui::FMOperatorParameter::AR, instFM->getEnvelopeParameter(FMEnvelopeParameter::AR1)); ui->op1Table->setValue(Ui::FMOperatorParameter::DR, instFM->getEnvelopeParameter(FMEnvelopeParameter::DR1)); ui->op1Table->setValue(Ui::FMOperatorParameter::SR, instFM->getEnvelopeParameter(FMEnvelopeParameter::SR1)); ui->op1Table->setValue(Ui::FMOperatorParameter::RR, instFM->getEnvelopeParameter(FMEnvelopeParameter::RR1)); ui->op1Table->setValue(Ui::FMOperatorParameter::SL, instFM->getEnvelopeParameter(FMEnvelopeParameter::SL1)); ui->op1Table->setValue(Ui::FMOperatorParameter::TL, instFM->getEnvelopeParameter(FMEnvelopeParameter::TL1)); ui->op1Table->setValue(Ui::FMOperatorParameter::KS, instFM->getEnvelopeParameter(FMEnvelopeParameter::KS1)); ui->op1Table->setValue(Ui::FMOperatorParameter::ML, instFM->getEnvelopeParameter(FMEnvelopeParameter::ML1)); ui->op1Table->setValue(Ui::FMOperatorParameter::DT, instFM->getEnvelopeParameter(FMEnvelopeParameter::DT1)); ui->op1Table->setValue(Ui::FMOperatorParameter::SSGEG, instFM->getEnvelopeParameter(FMEnvelopeParameter::SSGEG1)); ui->op1Table->setGroupEnabled(instFM->getOperatorEnabled(0)); ui->op2Table->setValue(Ui::FMOperatorParameter::AR, instFM->getEnvelopeParameter(FMEnvelopeParameter::AR2)); ui->op2Table->setValue(Ui::FMOperatorParameter::DR, instFM->getEnvelopeParameter(FMEnvelopeParameter::DR2)); ui->op2Table->setValue(Ui::FMOperatorParameter::SR, instFM->getEnvelopeParameter(FMEnvelopeParameter::SR2)); ui->op2Table->setValue(Ui::FMOperatorParameter::RR, instFM->getEnvelopeParameter(FMEnvelopeParameter::RR2)); ui->op2Table->setValue(Ui::FMOperatorParameter::SL, instFM->getEnvelopeParameter(FMEnvelopeParameter::SL2)); ui->op2Table->setValue(Ui::FMOperatorParameter::TL, instFM->getEnvelopeParameter(FMEnvelopeParameter::TL2)); ui->op2Table->setValue(Ui::FMOperatorParameter::KS, instFM->getEnvelopeParameter(FMEnvelopeParameter::KS2)); ui->op2Table->setValue(Ui::FMOperatorParameter::ML, instFM->getEnvelopeParameter(FMEnvelopeParameter::ML2)); ui->op2Table->setValue(Ui::FMOperatorParameter::DT, instFM->getEnvelopeParameter(FMEnvelopeParameter::DT2)); ui->op2Table->setValue(Ui::FMOperatorParameter::SSGEG, instFM->getEnvelopeParameter(FMEnvelopeParameter::SSGEG2)); ui->op2Table->setGroupEnabled(instFM->getOperatorEnabled(1)); ui->op3Table->setValue(Ui::FMOperatorParameter::AR, instFM->getEnvelopeParameter(FMEnvelopeParameter::AR3)); ui->op3Table->setValue(Ui::FMOperatorParameter::DR, instFM->getEnvelopeParameter(FMEnvelopeParameter::DR3)); ui->op3Table->setValue(Ui::FMOperatorParameter::SR, instFM->getEnvelopeParameter(FMEnvelopeParameter::SR3)); ui->op3Table->setValue(Ui::FMOperatorParameter::RR, instFM->getEnvelopeParameter(FMEnvelopeParameter::RR3)); ui->op3Table->setValue(Ui::FMOperatorParameter::SL, instFM->getEnvelopeParameter(FMEnvelopeParameter::SL3)); ui->op3Table->setValue(Ui::FMOperatorParameter::TL, instFM->getEnvelopeParameter(FMEnvelopeParameter::TL3)); ui->op3Table->setValue(Ui::FMOperatorParameter::KS, instFM->getEnvelopeParameter(FMEnvelopeParameter::KS3)); ui->op3Table->setValue(Ui::FMOperatorParameter::ML, instFM->getEnvelopeParameter(FMEnvelopeParameter::ML3)); ui->op3Table->setValue(Ui::FMOperatorParameter::DT, instFM->getEnvelopeParameter(FMEnvelopeParameter::DT3)); ui->op3Table->setValue(Ui::FMOperatorParameter::SSGEG, instFM->getEnvelopeParameter(FMEnvelopeParameter::SSGEG3)); ui->op3Table->setGroupEnabled(instFM->getOperatorEnabled(2)); ui->op4Table->setValue(Ui::FMOperatorParameter::AR, instFM->getEnvelopeParameter(FMEnvelopeParameter::AR4)); ui->op4Table->setValue(Ui::FMOperatorParameter::DR, instFM->getEnvelopeParameter(FMEnvelopeParameter::DR4)); ui->op4Table->setValue(Ui::FMOperatorParameter::SR, instFM->getEnvelopeParameter(FMEnvelopeParameter::SR4)); ui->op4Table->setValue(Ui::FMOperatorParameter::RR, instFM->getEnvelopeParameter(FMEnvelopeParameter::RR4)); ui->op4Table->setValue(Ui::FMOperatorParameter::SL, instFM->getEnvelopeParameter(FMEnvelopeParameter::SL4)); ui->op4Table->setValue(Ui::FMOperatorParameter::TL, instFM->getEnvelopeParameter(FMEnvelopeParameter::TL4)); ui->op4Table->setValue(Ui::FMOperatorParameter::KS, instFM->getEnvelopeParameter(FMEnvelopeParameter::KS4)); ui->op4Table->setValue(Ui::FMOperatorParameter::ML, instFM->getEnvelopeParameter(FMEnvelopeParameter::ML4)); ui->op4Table->setValue(Ui::FMOperatorParameter::DT, instFM->getEnvelopeParameter(FMEnvelopeParameter::DT4)); ui->op4Table->setValue(Ui::FMOperatorParameter::SSGEG, instFM->getEnvelopeParameter(FMEnvelopeParameter::SSGEG4)); ui->op4Table->setGroupEnabled(instFM->getOperatorEnabled(3)); } void FmInstrumentEditor::setInstrumentEnvelopeParameters(QString data) { QRegularExpression re("^(?\\d+),(?\\d+),\\s*" "(?\\d+),(?\\d+),(?\\d+),(?\\d+),(?\\d+)," "(?\\d+),(?\\d+),(?\\d+),(?\\d+),(?-?\\d+),\\s*" "(?\\d+),(?\\d+),(?\\d+),(?\\d+),(?\\d+)," "(?\\d+),(?\\d+),(?\\d+),(?\\d+),(?-?\\d+),\\s*" "(?\\d+),(?\\d+),(?\\d+),(?\\d+),(?\\d+)," "(?\\d+),(?\\d+),(?\\d+),(?\\d+),(?-?\\d+),\\s*" "(?\\d+),(?\\d+),(?\\d+),(?\\d+),(?\\d+)," "(?\\d+),(?\\d+),(?\\d+),(?\\d+),(?-?\\d+),?\\s*"); QRegularExpressionMatch match = re.match(data); if (match.hasMatch()) { ui->fbSlider->setValue(match.captured("fb").toInt()); ui->alSlider->setValue(match.captured("al").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::AR, match.captured("ar1").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::DR, match.captured("dr1").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::SR, match.captured("sr1").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::RR, match.captured("rr1").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::SL, match.captured("sl1").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::TL, match.captured("tl1").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::KS, match.captured("ks1").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::ML, match.captured("ml1").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::DT, match.captured("dt1").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::SSGEG, match.captured("ssgeg1").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::AR, match.captured("ar2").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::DR, match.captured("dr2").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::SR, match.captured("sr2").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::RR, match.captured("rr2").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::SL, match.captured("sl2").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::TL, match.captured("tl2").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::KS, match.captured("ks2").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::ML, match.captured("ml2").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::DT, match.captured("dt2").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::SSGEG, match.captured("ssgeg2").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::AR, match.captured("ar3").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::DR, match.captured("dr3").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::SR, match.captured("sr3").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::RR, match.captured("rr3").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::SL, match.captured("sl3").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::TL, match.captured("tl3").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::KS, match.captured("ks3").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::ML, match.captured("ml3").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::DT, match.captured("dt3").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::SSGEG, match.captured("ssgeg3").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::AR, match.captured("ar4").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::DR, match.captured("dr4").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::SR, match.captured("sr4").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::RR, match.captured("rr4").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::SL, match.captured("sl4").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::TL, match.captured("tl4").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::KS, match.captured("ks4").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::ML, match.captured("ml4").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::DT, match.captured("dt4").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::SSGEG, match.captured("ssgeg4").toInt()); } } void FmInstrumentEditor::setInstrumentEnvelopeParameters(int envTypeNum, QString data) { QStringList digits; QRegularExpression re(R"((-?\d+))"); auto it = re.globalMatch(data); while (it.hasNext()) { auto match = it.next(); digits.append(match.captured(1)); } std::vector set = config_.lock()->getFMEnvelopeTexts().at(static_cast(envTypeNum)).texts; if (static_cast(set.size()) != digits.size()) { auto name = config_.lock()->getFMEnvelopeTexts().at(static_cast(envTypeNum)).name; QMessageBox::critical(this, tr("Error"), tr("Did not match the clipboard text format with %1.") .arg(gui_utils::utf8ToQString(name))); return; } auto rangeCheck = [](int v, int min, int max) -> int { if (v < min || max < v) throw std::out_of_range(""); else return v; }; auto rangeCheckDT = [rangeCheck](int v) -> int { switch (v) { case -3: v = 7; break; case -2: v = 6; break; case -1: v = 5; break; default: break; } return rangeCheck(v, 0, 7); }; try { for (int i = 0; i < digits.size(); ++i) { int d = digits[i].toInt(); switch (set[static_cast(i)]) { case FMEnvelopeTextType::Skip: break; case FMEnvelopeTextType::AL: ui->alSlider->setValue(rangeCheck(d, 0, 7)); break; case FMEnvelopeTextType::FB: ui->fbSlider->setValue(rangeCheck(d, 0, 7)); break; case FMEnvelopeTextType::AR1: ui->op1Table->setValue(Ui::FMOperatorParameter::AR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::DR1: ui->op1Table->setValue(Ui::FMOperatorParameter::DR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::SR1: ui->op1Table->setValue(Ui::FMOperatorParameter::SR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::RR1: ui->op1Table->setValue(Ui::FMOperatorParameter::RR, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::SL1: ui->op1Table->setValue(Ui::FMOperatorParameter::SL, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::TL1: ui->op1Table->setValue(Ui::FMOperatorParameter::TL, rangeCheck(d, 0, 127)); break; case FMEnvelopeTextType::KS1: ui->op1Table->setValue(Ui::FMOperatorParameter::KS, rangeCheck(d, 0, 3)); break; case FMEnvelopeTextType::ML1: ui->op1Table->setValue(Ui::FMOperatorParameter::ML, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::DT1: ui->op1Table->setValue(Ui::FMOperatorParameter::DT, rangeCheckDT(d)); break; case FMEnvelopeTextType::AR2: ui->op2Table->setValue(Ui::FMOperatorParameter::AR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::DR2: ui->op2Table->setValue(Ui::FMOperatorParameter::DR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::SR2: ui->op2Table->setValue(Ui::FMOperatorParameter::SR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::RR2: ui->op2Table->setValue(Ui::FMOperatorParameter::RR, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::SL2: ui->op2Table->setValue(Ui::FMOperatorParameter::SL, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::TL2: ui->op2Table->setValue(Ui::FMOperatorParameter::TL, rangeCheck(d, 0, 127)); break; case FMEnvelopeTextType::KS2: ui->op2Table->setValue(Ui::FMOperatorParameter::KS, rangeCheck(d, 0, 3)); break; case FMEnvelopeTextType::ML2: ui->op2Table->setValue(Ui::FMOperatorParameter::ML, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::DT2: ui->op2Table->setValue(Ui::FMOperatorParameter::DT, rangeCheckDT(d)); break; case FMEnvelopeTextType::AR3: ui->op3Table->setValue(Ui::FMOperatorParameter::AR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::DR3: ui->op3Table->setValue(Ui::FMOperatorParameter::DR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::SR3: ui->op3Table->setValue(Ui::FMOperatorParameter::SR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::RR3: ui->op3Table->setValue(Ui::FMOperatorParameter::RR, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::SL3: ui->op3Table->setValue(Ui::FMOperatorParameter::SL, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::TL3: ui->op3Table->setValue(Ui::FMOperatorParameter::TL, rangeCheck(d, 0, 127)); break; case FMEnvelopeTextType::KS3: ui->op3Table->setValue(Ui::FMOperatorParameter::KS, rangeCheck(d, 0, 4)); break; case FMEnvelopeTextType::ML3: ui->op3Table->setValue(Ui::FMOperatorParameter::ML, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::DT3: ui->op3Table->setValue(Ui::FMOperatorParameter::DT, rangeCheckDT(d)); break; case FMEnvelopeTextType::AR4: ui->op4Table->setValue(Ui::FMOperatorParameter::AR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::DR4: ui->op4Table->setValue(Ui::FMOperatorParameter::DR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::SR4: ui->op4Table->setValue(Ui::FMOperatorParameter::SR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::RR4: ui->op4Table->setValue(Ui::FMOperatorParameter::RR, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::SL4: ui->op4Table->setValue(Ui::FMOperatorParameter::SL, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::TL4: ui->op4Table->setValue(Ui::FMOperatorParameter::TL, rangeCheck(d, 0, 127)); break; case FMEnvelopeTextType::KS4: ui->op4Table->setValue(Ui::FMOperatorParameter::KS, rangeCheck(d, 0, 4)); break; case FMEnvelopeTextType::ML4: ui->op4Table->setValue(Ui::FMOperatorParameter::ML, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::DT4: ui->op4Table->setValue(Ui::FMOperatorParameter::DT, rangeCheckDT(d)); break; } } } catch (...) { auto name = config_.lock()->getFMEnvelopeTexts().at(static_cast(envTypeNum)).name; QMessageBox::critical(this, tr("Error"), tr("Did not match the clipboard text format with %1.") .arg(gui_utils::utf8ToQString(name))); return; } } void FmInstrumentEditor::setInstrumentOperatorParameters(int opNum, QString data) { QRegularExpression re("^(?\\d+),(?\\d+),(?\\d+),(?\\d+),(?\\d+)," "(?\\d+),(?\\d+),(?\\d+),(?
\\d+),(?-?\\d+)"); QRegularExpressionMatch match = re.match(data); if (match.hasMatch()) { switch (opNum) { case 0: ui->op1Table->setValue(Ui::FMOperatorParameter::AR, match.captured("ar").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::DR, match.captured("dr").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::SR, match.captured("sr").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::RR, match.captured("rr").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::SL, match.captured("sl").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::TL, match.captured("tl").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::KS, match.captured("ks").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::ML, match.captured("ml").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::DT, match.captured("dt").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::SSGEG, match.captured("ssgeg").toInt()); break; case 1: ui->op2Table->setValue(Ui::FMOperatorParameter::AR, match.captured("ar").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::DR, match.captured("dr").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::SR, match.captured("sr").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::RR, match.captured("rr").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::SL, match.captured("sl").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::TL, match.captured("tl").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::KS, match.captured("ks").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::ML, match.captured("ml").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::DT, match.captured("dt").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::SSGEG, match.captured("ssgeg").toInt()); break; case 2: ui->op3Table->setValue(Ui::FMOperatorParameter::AR, match.captured("ar").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::DR, match.captured("dr").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::SR, match.captured("sr").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::RR, match.captured("rr").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::SL, match.captured("sl").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::TL, match.captured("tl").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::KS, match.captured("ks").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::ML, match.captured("ml").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::DT, match.captured("dt").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::SSGEG, match.captured("ssgeg").toInt()); break; case 3: ui->op4Table->setValue(Ui::FMOperatorParameter::AR, match.captured("ar").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::DR, match.captured("dr").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::SR, match.captured("sr").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::RR, match.captured("rr").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::SL, match.captured("sl").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::TL, match.captured("tl").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::KS, match.captured("ks").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::ML, match.captured("ml").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::DT, match.captured("dt").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::SSGEG, match.captured("ssgeg").toInt()); break; } } } void FmInstrumentEditor::paintAlgorithmDiagram() { if (!palette_) return; ui->alGraphicsView->setBackgroundBrush(QBrush(palette_->instFMAlBackColor)); QGraphicsScene* scene = ui->alGraphicsView->scene(); // 200 * 70 scene->clear(); QPen pen(palette_->instFMAlForeColor); QBrush brush(palette_->instFMAlForeColor); auto one = new QGraphicsSimpleTextItem(); one->setBrush(brush); one->setText("1"); auto two = new QGraphicsSimpleTextItem(); two->setBrush(brush); two->setText("2"); auto three = new QGraphicsSimpleTextItem(); three->setBrush(brush); three->setText("3"); auto four = new QGraphicsSimpleTextItem(); four->setBrush(brush); four->setText("4"); switch (ui->alSlider->value()) { case 0: { scene->addRect(8, -4, 16, 8, pen, brush); scene->addRect(40, -4, 16, 8, pen, brush); scene->addRect(72, -4, 16, 8, pen, brush); scene->addRect(104, -4, 16, 8, pen, brush); scene->addLine(0, 0, 128, 0, pen); scene->addLine(0, -8, 32, -8, pen); scene->addLine(0, 0, 0, -8, pen); scene->addLine(32, 0, 32, -8, pen); one->setPos(14, 8); two->setPos(46, 8); three->setPos(78, 8); four->setPos(110, 8); break; } case 1: { scene->addRect(8, -12, 16, 8, pen, brush); scene->addRect(8, 4, 16, 8, pen, brush); scene->addRect(40, -4, 16, 8, pen, brush); scene->addRect(72, -4, 16, 8, pen, brush); scene->addLine(0, -8, 35, -8, pen); scene->addLine(24, 8, 35, 8, pen); scene->addLine(0, -16, 29, -16, pen); scene->addLine(0, -8, 0, -16, pen); scene->addLine(29, -8, 29, -16, pen); scene->addLine(35, -8, 35, 8, pen); scene->addLine(35, 0, 96, 0, pen); one->setPos(-8, -12); two->setPos(-8, 4); three->setPos(46, 8); four->setPos(78, 8); break; } case 2: { scene->addRect(40, -12, 16, 8, pen, brush); scene->addRect(8, 4, 16, 8, pen, brush); scene->addRect(40, 4, 16, 8, pen, brush); scene->addRect(72, -4, 16, 8, pen, brush); scene->addLine(32, -8, 67, -8, pen); scene->addLine(32, -8, 32, -16, pen); scene->addLine(61, -8, 61, -16, pen); scene->addLine(32, -16, 61, -16, pen); scene->addLine(24, 8, 67, 8, pen); scene->addLine(67, -8, 67, 8, pen); scene->addLine(67, 0, 96, 0, pen); one->setPos(24, -12); two->setPos(14, 16); three->setPos(46, 16); four->setPos(78, 8); break; } case 3: { scene->addRect(8, -12, 16, 8, pen, brush); scene->addRect(40, -12, 16, 8, pen, brush); scene->addRect(40, 4, 16, 8, pen, brush); scene->addRect(72, -4, 16, 8, pen, brush); scene->addLine(0, -8, 64, -8, pen); scene->addLine(0, -8, 0, -16, pen); scene->addLine(32, -8, 32, -16, pen); scene->addLine(0, -16, 32, -16, pen); scene->addLine(56, 8, 64, 8, pen); scene->addLine(64, -8, 64, 8, pen); scene->addLine(64, 0, 96, 0, pen); one->setPos(-8, -12); two->setPos(46, -24); three->setPos(46, 16); four->setPos(78, 8); break; } case 4: { scene->addRect(8, -12, 16, 8, pen, brush); scene->addRect(40, -12, 16, 8, pen, brush); scene->addRect(8, 4, 16, 8, pen, brush); scene->addRect(40, 4, 16, 8, pen, brush); scene->addLine(0, -8, 64, -8, pen); scene->addLine(0, -8, 0, -16, pen); scene->addLine(32, -8, 32, -16, pen); scene->addLine(0, -16, 32, -16, pen); scene->addLine(24, 8, 64, 8, pen); scene->addLine(64, -8, 64, 8, pen); scene->addLine(64, 0, 72, 0, pen); one->setPos(-8, -12); two->setPos(46, -24); three->setPos(14, 16); four->setPos(46, 16); break; } case 5: { scene->addRect(8, -4, 16, 8, pen, brush); scene->addRect(40, -20, 16, 8, pen, brush); scene->addRect(40, -4, 16, 8, pen, brush); scene->addRect(40, 12, 16, 8, pen, brush); scene->addLine(0, 0, 72, 0, pen); scene->addLine(0, 0, 0, -8, pen); scene->addLine(29, 0, 29, -8, pen); scene->addLine(0, -8, 29, -8, pen); scene->addLine(35, -16, 35, 16, pen); scene->addLine(64, -16, 64, 16, pen); scene->addLine(35, -16, 64, -16, pen); scene->addLine(35, 16, 64, 16, pen); one->setPos(14, 8); two->setPos(76, -20); three->setPos(76, -4); four->setPos(76, 12); break; } case 6: { scene->addRect(8, -20, 16, 8, pen, brush); scene->addRect(40, -20, 16, 8, pen, brush); scene->addRect(40, -4, 16, 8, pen, brush); scene->addRect(40, 12, 16, 8, pen, brush); scene->addLine(0, -16, 64, -16, pen); scene->addLine(0, -16, 0, -24, pen); scene->addLine(32, -16, 32, -24, pen); scene->addLine(0, -24, 32, -24, pen); scene->addLine(56, 0, 72, 0, pen); scene->addLine(56, 16, 64, 16, pen); scene->addLine(64, -16, 64, 16, pen); one->setPos(14, -8); two->setPos(76, -20); three->setPos(76, -4); four->setPos(76, 12); break; } case 7: { scene->addRect(-28, 8, 8, 16, pen, brush); scene->addRect(-12, 8, 8, 16, pen, brush); scene->addRect(4, 8, 8, 16, pen, brush); scene->addRect(20, 8, 8, 16, pen, brush); scene->addLine(-24, 0, -24, 35, pen); scene->addLine(-24, 0, -32, 0, pen); scene->addLine(-24, 29, -32, 29, pen); scene->addLine(-32, 0, -32, 29, pen); scene->addLine(-8, 24, -8, 35, pen); scene->addLine(8, 24, 8, 35, pen); scene->addLine(24, 24, 24, 35, pen); scene->addLine(-24, 35, 24, 35, pen); scene->addLine(0, 35, 0, 40, pen); one->setPos(-26, -12); two->setPos(-10, -12); three->setPos(6, -12); four->setPos(22, -12); break; } } scene->addItem(one); scene->addItem(two); scene->addItem(three); scene->addItem(four); scene->setSceneRect(scene->itemsBoundingRect()); } void FmInstrumentEditor::resizeAlgorithmDiagram() { ui->alGraphicsView->fitInView(ui->alGraphicsView->scene()->itemsBoundingRect(), Qt::AspectRatioMode::KeepAspectRatio); } /********** Slots **********/ void FmInstrumentEditor::copyEnvelope() { QApplication::clipboard()->setText(QString("FM_ENVELOPE:%1,%2,\n%3,\n%4,\n%5,\n%6,") .arg(ui->fbSlider->value()).arg(ui->alSlider->value()) .arg(ui->op1Table->toString(), ui->op2Table->toString(), ui->op3Table->toString(), ui->op4Table->toString())); } void FmInstrumentEditor::pasteEnvelope() { QString data = QApplication::clipboard()->text().remove("FM_ENVELOPE:"); setInstrumentEnvelopeParameters(data); } void FmInstrumentEditor::pasteEnvelopeFrom(int typenum) { setInstrumentEnvelopeParameters(typenum, QApplication::clipboard()->text()); } void FmInstrumentEditor::copyOperator(int opNum) { QString text; switch (opNum) { case 0: text = ui->op1Table->toString(); break; case 1: text = ui->op2Table->toString(); break; case 2: text = ui->op3Table->toString(); break; case 3: text = ui->op4Table->toString(); break; } QApplication::clipboard()->setText(QString("FM_OPERATOR:") + text); } void FmInstrumentEditor::pasteOperator(int opNum) { QString data = QApplication::clipboard()->text().remove("FM_OPERATOR:"); setInstrumentOperatorParameters(opNum, data); } void FmInstrumentEditor::on_envNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMEnvelope(instNum_, arg1); setInstrumentEnvelopeParameters(); emit envelopeNumberChanged(); emit modified(); } onEnvelopeNumberChanged(); } void FmInstrumentEditor::on_envGroupBox_customContextMenuRequested(const QPoint &pos) { QPoint globalPos = ui->envGroupBox->mapToGlobal(pos); QMenu menu; // Leave Before Qt5.7.0 style due to windows xp QAction* copy = menu.addAction(tr("Copy envelope")); QObject::connect(copy, &QAction::triggered, this, &FmInstrumentEditor::copyEnvelope); QAction* paste = menu.addAction(tr("Paste envelope")); QObject::connect(paste, &QAction::triggered, this, &FmInstrumentEditor::pasteEnvelope); paste->setEnabled(QApplication::clipboard()->text().startsWith("FM_ENVELOPE:")); QMenu* pasteFrom = menu.addMenu(tr("Paste envelope From")); std::vector textsSet = config_.lock()->getFMEnvelopeTexts(); for (size_t i = 0; i < textsSet.size(); ++i) { QAction* act = pasteFrom->addAction(gui_utils::utf8ToQString(textsSet[i].name)); act->setData(static_cast(i)); } QObject::connect(pasteFrom, &QMenu::triggered, this, [&](QAction* action) { pasteEnvelopeFrom(action->data().toInt()); }); menu.exec(globalPos); } void FmInstrumentEditor::onEnvelopeParameterChanged(int envNum) { if (ui->envNumSpinBox->value() == envNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentEnvelopeParameters(); } } void FmInstrumentEditor::onEnvelopeNumberChanged() { // Change users view std::multiset users = bt_.lock()->getEnvelopeFMUsers(ui->envNumSpinBox->value()); ui->envUsersLineEdit->setText(inst_edit_utils::generateUsersString(users)); } //========== LFO ==========// void FmInstrumentEditor::setInstrumentLFOParameters() { Ui::EventGuard eg(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instFM = dynamic_cast(inst.get()); ui->lfoNumSpinBox->setValue(instFM->getLFONumber()); ui->lfoFreqSlider->setValue(instFM->getLFOParameter(FMLFOParameter::FREQ)); ui->pmsSlider->setValue(instFM->getLFOParameter(FMLFOParameter::PMS)); ui->amsSlider->setValue(instFM->getLFOParameter(FMLFOParameter::AMS)); ui->amOp1CheckBox->setChecked(instFM->getLFOParameter(FMLFOParameter::AM1)); ui->amOp2CheckBox->setChecked(instFM->getLFOParameter(FMLFOParameter::AM2)); ui->amOp3CheckBox->setChecked(instFM->getLFOParameter(FMLFOParameter::AM3)); ui->amOp4CheckBox->setChecked(instFM->getLFOParameter(FMLFOParameter::AM4)); ui->lfoStartSpinBox->setValue(instFM->getLFOParameter(FMLFOParameter::Count)); if (instFM->getLFOEnabled()) { ui->lfoGroupBox->setChecked(true); onLFONumberChanged(); } else { ui->lfoGroupBox->setChecked(false); } } void FmInstrumentEditor::setInstrumentLFOParameters(QString data) { QRegularExpression re("^(?\\d+),(?\\d+),(?\\d+),(?\\d+)," "(?\\d+),(?\\d+),(?\\d+),(?\\d+)"); QRegularExpressionMatch match = re.match(data); if (match.hasMatch()) { ui->lfoFreqSlider->setValue(match.captured("freq").toInt()); ui->pmsSlider->setValue(match.captured("pms").toInt()); ui->amsSlider->setValue(match.captured("ams").toInt()); ui->lfoStartSpinBox->setValue(match.captured("cnt").toInt()); ui->amOp1CheckBox->setChecked(match.captured("am1").toInt() == 1); ui->amOp2CheckBox->setChecked(match.captured("am2").toInt() == 1); ui->amOp3CheckBox->setChecked(match.captured("am3").toInt() == 1); ui->amOp4CheckBox->setChecked(match.captured("am4").toInt() == 1); } } QString FmInstrumentEditor::toLFOString() const { auto str = QString("%1,%2,%3,%4,%5,%6,%7,%8") .arg(ui->lfoFreqSlider->value()) .arg(ui->pmsSlider->value()) .arg(ui->amsSlider->value()) .arg(ui->lfoStartSpinBox->value()) .arg(ui->amOp1CheckBox->isChecked() ? 1 : 0) .arg(ui->amOp2CheckBox->isChecked() ? 1 : 0) .arg(ui->amOp3CheckBox->isChecked() ? 1 : 0) .arg(ui->amOp4CheckBox->isChecked() ? 1 : 0); return str; } /********** Slots **********/ void FmInstrumentEditor::onLFOParameterChanged(int lfoNum) { if (ui->lfoNumSpinBox->value() == lfoNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentLFOParameters(); } } void FmInstrumentEditor::onLFONumberChanged() { // Change users view std::multiset users = bt_.lock()->getLFOFMUsers(ui->lfoNumSpinBox->value()); ui->lfoUsersLineEdit->setText(inst_edit_utils::generateUsersString(users)); } void FmInstrumentEditor::on_lfoGroupBox_customContextMenuRequested(const QPoint &pos) { QClipboard* clipboard = QApplication::clipboard(); QPoint globalPos = ui->lfoGroupBox->mapToGlobal(pos); QMenu menu; // Leave Before Qt5.7.0 style due to windows xp QAction* copy = menu.addAction(tr("Copy LFO parameters")); QObject::connect(copy, &QAction::triggered, this, [&, clipboard]() { clipboard->setText("FM_LFO:" + toLFOString()); }); QAction* paste = menu.addAction(tr("Paste LFO parameters")); QObject::connect(paste, &QAction::triggered, this, [&, clipboard]() { QString data = clipboard->text().remove("FM_LFO:"); setInstrumentLFOParameters(data); }); if (!ui->lfoGroupBox->isChecked()) { copy->setEnabled(false); paste->setEnabled(false); } else if (!clipboard->text().startsWith("FM_LFO:")) { paste->setEnabled(false); } menu.exec(globalPos); } void FmInstrumentEditor::on_lfoNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMLFO(instNum_, arg1); setInstrumentLFOParameters(); emit lfoNumberChanged(); emit modified(); } onLFONumberChanged(); } void FmInstrumentEditor::on_lfoGroupBox_toggled(bool arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMLFOEnabled(instNum_, arg1); setInstrumentLFOParameters(); emit lfoNumberChanged(); emit modified(); } onLFONumberChanged(); } //--- OperatorSequence FMEnvelopeParameter FmInstrumentEditor::getOperatorSequenceParameter() const { return static_cast(ui->opSeqTypeComboBox->currentData(Qt::UserRole).toInt()); } void FmInstrumentEditor::setInstrumentOperatorSequenceParameters() { Ui::EventGuard ev(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instFM = dynamic_cast(inst.get()); FMEnvelopeParameter param = getOperatorSequenceParameter(); ui->opSeqNumSpinBox->setValue(instFM->getOperatorSequenceNumber(param)); ui->opSeqEditor->clearData(); setOperatorSequenceEditor(); for (auto& unit : instFM->getOperatorSequenceSequence(param)) { ui->opSeqEditor->addSequenceData(unit.data); } for (auto& loop : instFM->getOperatorSequenceLoopRoot(param).getAllLoops()) { ui->opSeqEditor->addLoop(loop.getBeginPos(), loop.getEndPos(), loop.getTimes()); } ui->opSeqEditor->setRelease(instFM->getOperatorSequenceRelease(param)); if (instFM->getOperatorSequenceEnabled(param)) { ui->opSeqEditGroupBox->setChecked(true); onOperatorSequenceNumberChanged(); } else { ui->opSeqEditGroupBox->setChecked(false); } } void FmInstrumentEditor::setOperatorSequenceEditor() { ui->opSeqEditor->clearRow(); FMEnvelopeParameter param = getOperatorSequenceParameter(); switch (param) { case FMEnvelopeParameter::AL: case FMEnvelopeParameter::FB: case FMEnvelopeParameter::DT1: case FMEnvelopeParameter::DT2: case FMEnvelopeParameter::DT3: case FMEnvelopeParameter::DT4: ui->opSeqEditor->setMaximumDisplayedRowCount(8); for (int i = 0; i < 8; ++i) { ui->opSeqEditor->AddRow(QString::number(i), false); } ui->opSeqEditor->autoFitLabelWidth(); ui->opSeqEditor->setUpperRow(7); break; case FMEnvelopeParameter::AR1: case FMEnvelopeParameter::AR2: case FMEnvelopeParameter::AR3: case FMEnvelopeParameter::AR4: case FMEnvelopeParameter::DR1: case FMEnvelopeParameter::DR2: case FMEnvelopeParameter::DR3: case FMEnvelopeParameter::DR4: case FMEnvelopeParameter::SR1: case FMEnvelopeParameter::SR2: case FMEnvelopeParameter::SR3: case FMEnvelopeParameter::SR4: ui->opSeqEditor->setMaximumDisplayedRowCount(16); for (int i = 0; i < 32; ++i) { ui->opSeqEditor->AddRow(QString::number(i)); } ui->opSeqEditor->setUpperRow(15); break; case FMEnvelopeParameter::RR1: case FMEnvelopeParameter::RR2: case FMEnvelopeParameter::RR3: case FMEnvelopeParameter::RR4: case FMEnvelopeParameter::SL1: case FMEnvelopeParameter::SL2: case FMEnvelopeParameter::SL3: case FMEnvelopeParameter::SL4: case FMEnvelopeParameter::ML1: case FMEnvelopeParameter::ML2: case FMEnvelopeParameter::ML3: case FMEnvelopeParameter::ML4: ui->opSeqEditor->setMaximumDisplayedRowCount(16); for (int i = 0; i < 16; ++i) { ui->opSeqEditor->AddRow(QString::number(i)); } ui->opSeqEditor->setUpperRow(15); break; case FMEnvelopeParameter::KS1: case FMEnvelopeParameter::KS2: case FMEnvelopeParameter::KS3: case FMEnvelopeParameter::KS4: ui->opSeqEditor->setMaximumDisplayedRowCount(4); for (int i = 0; i < 4; ++i) { ui->opSeqEditor->AddRow(QString::number(i)); } ui->opSeqEditor->setUpperRow(3); break; case FMEnvelopeParameter::TL1: case FMEnvelopeParameter::TL2: case FMEnvelopeParameter::TL3: case FMEnvelopeParameter::TL4: ui->opSeqEditor->setMaximumDisplayedRowCount(16); for (int i = 0; i < 128; ++i) { ui->opSeqEditor->AddRow(QString::number(i)); } ui->opSeqEditor->setUpperRow(15); break; default: break; } } /********** Slots **********/ void FmInstrumentEditor::onOperatorSequenceNumberChanged() { // Change users view std::multiset users = bt_.lock()->getOperatorSequenceFMUsers(getOperatorSequenceParameter(), ui->opSeqNumSpinBox->value()); ui->opSeqUsersLineEdit->setText(inst_edit_utils::generateUsersString(users)); } void FmInstrumentEditor::onOperatorSequenceParameterChanged(FMEnvelopeParameter param, int tnNum) { if (param == getOperatorSequenceParameter() && ui->opSeqNumSpinBox->value() == tnNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentOperatorSequenceParameters(); } } void FmInstrumentEditor::onOperatorSequenceTypeChanged(int) { if (!isIgnoreEvent_) setInstrumentOperatorSequenceParameters(); } void FmInstrumentEditor::on_opSeqEditGroupBox_toggled(bool arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMOperatorSequenceEnabled(instNum_, getOperatorSequenceParameter(), arg1); setInstrumentOperatorSequenceParameters(); emit operatorSequenceNumberChanged(); emit modified(); } onOperatorSequenceNumberChanged(); } void FmInstrumentEditor::on_opSeqNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMOperatorSequence(instNum_, getOperatorSequenceParameter(), arg1); setInstrumentOperatorSequenceParameters(); emit operatorSequenceNumberChanged(); emit modified(); } onOperatorSequenceNumberChanged(); } //--- Arpeggio FMOperatorType FmInstrumentEditor::getArpeggioOperator() const { return static_cast(ui->arpOpComboBox->currentData(Qt::UserRole).toInt()); } void FmInstrumentEditor::setInstrumentArpeggioParameters() { Ui::EventGuard ev(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instFM = dynamic_cast(inst.get()); FMOperatorType param = getArpeggioOperator(); ui->arpNumSpinBox->setValue(instFM->getArpeggioNumber(param)); ui->arpEditor->clearData(); for (auto& unit : instFM->getArpeggioSequence(param)) { ui->arpEditor->addSequenceData(unit.data); } for (auto& loop : instFM->getArpeggioLoopRoot(param).getAllLoops()) { ui->arpEditor->addLoop(loop.getBeginPos(), loop.getEndPos(), loop.getTimes()); } ui->arpEditor->setRelease(instFM->getArpeggioRelease(param)); for (int i = 0; i < ui->arpTypeComboBox->count(); ++i) { if (instFM->getArpeggioType(param) == static_cast(ui->arpTypeComboBox->itemData(i).toInt())) { ui->arpTypeComboBox->setCurrentIndex(i); break; } } if (instFM->getArpeggioEnabled(param)) { ui->arpEditGroupBox->setChecked(true); onArpeggioNumberChanged(); } else { ui->arpEditGroupBox->setChecked(false); } } /********** Slots **********/ void FmInstrumentEditor::onArpeggioNumberChanged() { // Change users view std::multiset users = bt_.lock()->getArpeggioFMUsers(ui->arpNumSpinBox->value()); ui->arpUsersLineEdit->setText(inst_edit_utils::generateUsersString(users)); } void FmInstrumentEditor::onArpeggioParameterChanged(int tnNum) { if (ui->arpNumSpinBox->value() == tnNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentArpeggioParameters(); } } void FmInstrumentEditor::onArpeggioOperatorChanged(int) { if (!isIgnoreEvent_) setInstrumentArpeggioParameters(); } void FmInstrumentEditor::onArpeggioTypeChanged(int) { auto type = static_cast(ui->arpTypeComboBox->currentData(Qt::UserRole).toInt()); if (!isIgnoreEvent_) { bt_.lock()->setArpeggioFMType(ui->arpNumSpinBox->value(), type); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } ui->arpEditor->setSequenceType(type); } void FmInstrumentEditor::on_arpEditGroupBox_toggled(bool arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMArpeggioEnabled(instNum_, getArpeggioOperator(), arg1); setInstrumentArpeggioParameters(); emit arpeggioNumberChanged(); emit modified(); } onArpeggioNumberChanged(); } void FmInstrumentEditor::on_arpNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMArpeggio(instNum_, getArpeggioOperator(), arg1); setInstrumentArpeggioParameters(); emit arpeggioNumberChanged(); emit modified(); } onArpeggioNumberChanged(); } //--- Pitch FMOperatorType FmInstrumentEditor::getPitchOperator() const { return static_cast(ui->ptOpComboBox->currentData(Qt::UserRole).toInt()); } void FmInstrumentEditor::setInstrumentPitchParameters() { Ui::EventGuard ev(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instFM = dynamic_cast(inst.get()); FMOperatorType param = getPitchOperator(); ui->ptNumSpinBox->setValue(instFM->getPitchNumber(param)); ui->ptEditor->clearData(); for (auto& unit : instFM->getPitchSequence(param)) { ui->ptEditor->addSequenceData(unit.data); } for (auto& loop : instFM->getPitchLoopRoot(param).getAllLoops()) { ui->ptEditor->addLoop(loop.getBeginPos(), loop.getEndPos(), loop.getTimes()); } ui->ptEditor->setRelease(instFM->getPitchRelease(param)); for (int i = 0; i < ui->ptTypeComboBox->count(); ++i) { if (instFM->getPitchType(param) == static_cast(ui->ptTypeComboBox->itemData(i).toInt())) { ui->ptTypeComboBox->setCurrentIndex(i); break; } } if (instFM->getPitchEnabled(param)) { ui->ptEditGroupBox->setChecked(true); onPitchNumberChanged(); } else { ui->ptEditGroupBox->setChecked(false); } } /********** Slots **********/ void FmInstrumentEditor::onPitchNumberChanged() { // Change users view std::multiset users = bt_.lock()->getPitchFMUsers(ui->ptNumSpinBox->value()); ui->ptUsersLineEdit->setText(inst_edit_utils::generateUsersString(users)); } void FmInstrumentEditor::onPitchParameterChanged(int ptNum) { if (ui->ptNumSpinBox->value() == ptNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentPitchParameters(); } } void FmInstrumentEditor::onPitchOperatorChanged(int) { if (!isIgnoreEvent_) setInstrumentPitchParameters(); } void FmInstrumentEditor::onPitchTypeChanged(int) { auto type = static_cast(ui->ptTypeComboBox->currentData(Qt::UserRole).toInt()); if (!isIgnoreEvent_) { bt_.lock()->setPitchFMType(ui->ptNumSpinBox->value(), type); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } ui->ptEditor->setSequenceType(type); } void FmInstrumentEditor::on_ptEditGroupBox_toggled(bool arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMPitchEnabled(instNum_, getPitchOperator(), arg1); setInstrumentPitchParameters(); emit pitchNumberChanged(); emit modified(); } onPitchNumberChanged(); } void FmInstrumentEditor::on_ptNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMPitch(instNum_, getPitchOperator(), arg1); setInstrumentPitchParameters(); emit pitchNumberChanged(); emit modified(); } onPitchNumberChanged(); } //--- Pan void FmInstrumentEditor::setInstrumentPanParameters() { Ui::EventGuard ev(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instFM = dynamic_cast(inst.get()); ui->panNumSpinBox->setValue(instFM->getPanNumber()); ui->panEditor->clearData(); for (auto& unit : instFM->getPanSequence()) { ui->panEditor->addSequenceData(unit.data); } for (auto& loop : instFM->getPanLoopRoot().getAllLoops()) { ui->panEditor->addLoop(loop.getBeginPos(), loop.getEndPos(), loop.getTimes()); } ui->panEditor->setRelease(instFM->getPanRelease()); if (instFM->getPanEnabled()) { ui->panEditGroupBox->setChecked(true); onPanNumberChanged(); } else { ui->panEditGroupBox->setChecked(false); } } /********** Slots **********/ void FmInstrumentEditor::onPanNumberChanged() { // Change users view std::multiset users = bt_.lock()->getPanFMUsers(ui->panNumSpinBox->value()); ui->panUsersLineEdit->setText(inst_edit_utils::generateUsersString(users)); } void FmInstrumentEditor::onPanParameterChanged(int panNum) { if (ui->panNumSpinBox->value() == panNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentPanParameters(); } } void FmInstrumentEditor::on_panEditGroupBox_toggled(bool arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMPanEnabled(instNum_, arg1); setInstrumentPanParameters(); emit panNumberChanged(); emit modified(); } onPanNumberChanged(); } void FmInstrumentEditor::on_panNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMPan(instNum_, arg1); setInstrumentPanParameters(); emit panNumberChanged(); emit modified(); } onPanNumberChanged(); } //========== Others ==========// void FmInstrumentEditor::setInstrumentEnvelopeResetParameters() { Ui::EventGuard ev(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instFM = dynamic_cast(inst.get()); ui->envResetCheckBox->setChecked(instFM->getEnvelopeResetEnabled(FMOperatorType::All)); ui->envResetOp1CheckBox->setChecked(instFM->getEnvelopeResetEnabled(FMOperatorType::Op1)); ui->envResetOp2CheckBox->setChecked(instFM->getEnvelopeResetEnabled(FMOperatorType::Op2)); ui->envResetOp3CheckBox->setChecked(instFM->getEnvelopeResetEnabled(FMOperatorType::Op3)); ui->envResetOp4CheckBox->setChecked(instFM->getEnvelopeResetEnabled(FMOperatorType::Op4)); } BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/fm_instrument_editor.hpp000066400000000000000000000133331476276175200303070ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include #include "instrument.hpp" #include "gui/instrument_editor/instrument_editor.hpp" #include "gui/instrument_editor/visualized_instrument_macro_editor.hpp" namespace Ui { class FmInstrumentEditor; } class FmInstrumentEditor final : public InstrumentEditor { Q_OBJECT public: FmInstrumentEditor(int num, QWidget* parent = nullptr); ~FmInstrumentEditor() override; /** * @brief Return sound source of instrument related to this dialog. * @return FM. */ SoundSource getSoundSource() const override; /** * @brief Return instrument type of instrument related to this dialog. * @return FM. */ InstrumentType getInstrumentType() const override; void updateByConfigurationChange() override; protected: void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; void showEvent(QShowEvent*) override; void resizeEvent(QResizeEvent*) override; private: Ui::FmInstrumentEditor *ui; bool isIgnoreEvent_; // FM specific process called in settiing core / cofiguration / color palette. void updateBySettingCore() override; void updateBySettingConfiguration() override; void updateBySettingColorPalette() override; void updateInstrumentParameters(); //========== Envelope ==========// signals: void envelopeNumberChanged(); void envelopeParameterChanged(int envNum, int fromInstNum); public slots: void onEnvelopeParameterChanged(int envNum); void onEnvelopeNumberChanged(); private: void setInstrumentEnvelopeParameters(); void setInstrumentEnvelopeParameters(QString data); void setInstrumentEnvelopeParameters(int envTypeNum, QString data); void setInstrumentOperatorParameters(int opNum, QString data); void paintAlgorithmDiagram(); void resizeAlgorithmDiagram(); private slots: void copyEnvelope(); void pasteEnvelope(); void pasteEnvelopeFrom(int typenum); void copyOperator(int opNum); void pasteOperator(int opNum); void on_envNumSpinBox_valueChanged(int arg1); void on_envGroupBox_customContextMenuRequested(const QPoint &pos); //========== LFO ==========// signals: void lfoNumberChanged(); void lfoParameterChanged(int lfoNum, int fromInstNum); public slots: void onLFOParameterChanged(int lfoNum); void onLFONumberChanged(); private: void setInstrumentLFOParameters(); void setInstrumentLFOParameters(QString data); QString toLFOString() const; private slots: void on_lfoGroupBox_customContextMenuRequested(const QPoint &pos); void on_lfoNumSpinBox_valueChanged(int arg1); void on_lfoGroupBox_toggled(bool arg1); //========== OperatorSequence ==========// public: FMEnvelopeParameter getOperatorSequenceParameter() const; signals: void operatorSequenceNumberChanged(); void operatorSequenceParameterChanged(FMEnvelopeParameter param, int opSeqNum, int fromInstNum); public slots: void onOperatorSequenceNumberChanged(); void onOperatorSequenceParameterChanged(FMEnvelopeParameter param, int opSeqNum); private: void setInstrumentOperatorSequenceParameters(); void setOperatorSequenceEditor(); private slots: void onOperatorSequenceTypeChanged(int); void on_opSeqEditGroupBox_toggled(bool arg1); void on_opSeqNumSpinBox_valueChanged(int arg1); //========== Arpeggio ==========// public: FMOperatorType getArpeggioOperator() const; signals: void arpeggioNumberChanged(); void arpeggioParameterChanged(int arpNum, int fromInstNum); public slots: void onArpeggioNumberChanged(); void onArpeggioParameterChanged(int arpNum); private: void setInstrumentArpeggioParameters(); private slots: void onArpeggioOperatorChanged(int); void onArpeggioTypeChanged(int); void on_arpEditGroupBox_toggled(bool arg1); void on_arpNumSpinBox_valueChanged(int arg1); //========== Pitch ==========// public: FMOperatorType getPitchOperator() const; signals: void pitchNumberChanged(); void pitchParameterChanged(int ptNum, int fromInstNum); public slots: void onPitchNumberChanged(); void onPitchParameterChanged(int ptNum); private: void setInstrumentPitchParameters(); private slots: void onPitchOperatorChanged(int); void onPitchTypeChanged(int); void on_ptEditGroupBox_toggled(bool arg1); void on_ptNumSpinBox_valueChanged(int arg1); //========== Pan ==========// signals: void panNumberChanged(); void panParameterChanged(int panNum, int fromInstNum); public slots: void onPanNumberChanged(); void onPanParameterChanged(int panNum); private: void setInstrumentPanParameters(); private slots: void on_panEditGroupBox_toggled(bool arg1); void on_panNumSpinBox_valueChanged(int arg1); //========== Others ==========// private: void setInstrumentEnvelopeResetParameters(); }; BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/fm_instrument_editor.ui000066400000000000000000001017701476276175200301400ustar00rootroot00000000000000 FmInstrumentEditor 0 0 570 750 Form 0 0 0 0 QFrame::NoFrame 0 true 0 0 570 750 0 Envelope Envelope QFrame::StyledPanel QFrame::Raised QFrame::StyledPanel QFrame::Raised false # 127 Users: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter true Qt::NoFocus true QFrame::StyledPanel QFrame::Raised QFrame::StyledPanel QFrame::Raised QFrame::StyledPanel QFrame::Raised QFrame::StyledPanel QFrame::Raised LFO/Operator sequence Operator sequence Operator: Qt::Horizontal 40 20 Sequence true false false # 127 Qt::Horizontal 40 20 Users: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::NoFocus true LFO true false Qt::NoFocus true false # 127 Users: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter QFrame::StyledPanel QFrame::Raised QFrame::StyledPanel QFrame::Raised QFrame::StyledPanel QFrame::Raised Start count: false AM operators Operator 1 Operator 2 Operator 3 Operator 4 Qt::Vertical 20 40 Envelope reset Reset envelope before key on false FM 3ch Operator 1 Operator 2 Operator 3 Operator 4 Qt::Vertical 20 40 Arpeggio/Pitch Arpeggio Operator: Sequence true false Type: Users: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false # 127 Qt::Horizontal 40 20 Qt::NoFocus true Qt::Horizontal 40 20 Pitch Operator: Qt::Horizontal 40 20 Sequence true false Qt::Horizontal 40 20 false # 127 Type: Users: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::NoFocus true Panning Panning true false false # 127 Users: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::NoFocus true Qt::Horizontal 185 20 Qt::Vertical 20 40 VisualizedInstrumentMacroEditor QWidget
gui/instrument_editor/visualized_instrument_macro_editor.hpp
1
ArpeggioMacroEditor QWidget
gui/instrument_editor/arpeggio_macro_editor.hpp
1
PanMacroEditor QWidget
gui/instrument_editor/pan_macro_editor.hpp
1
LabeledVerticalSlider QFrame
gui/labeled_vertical_slider.hpp
1
FMOperatorTable QFrame
gui/instrument_editor/fm_operator_table.hpp
1
LabeledHorizontalSlider QFrame
gui/labeled_horizontal_slider.hpp
1
scrollArea tabWidget envNumSpinBox alGraphicsView lfoGroupBox lfoNumSpinBox lfoStartSpinBox amOp1CheckBox amOp2CheckBox amOp3CheckBox amOp4CheckBox envResetCheckBox envResetOp1CheckBox envResetOp2CheckBox envResetOp3CheckBox envResetOp4CheckBox opSeqTypeComboBox opSeqEditGroupBox opSeqNumSpinBox arpOpComboBox arpEditGroupBox arpNumSpinBox arpTypeComboBox ptOpComboBox ptEditGroupBox ptNumSpinBox ptTypeComboBox panEditGroupBox panNumSpinBox
BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/fm_operator_table.cpp000066400000000000000000000334451476276175200275340ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "fm_operator_table.hpp" #include "ui_fm_operator_table.h" #include #include #include #include #include "gui/event_guard.hpp" namespace { constexpr int DT_SIGN_TBL_[8] = { 0, 1, 2, 3, 0, -1, -2, -3 }; constexpr int DT_UNSIGN_TBL_[7] = { 7, 6, 5, 0, 1, 2, 3 }; } FMOperatorTable::FMOperatorTable(QWidget *parent) : QFrame(parent), ui(new Ui::FMOperatorTable), isDTNegative_(false), isIgnoreEvent_(false) { ui->setupUi(this); ui->groupBox->setContextMenuPolicy(Qt::CustomContextMenu); // Init sliders ui->arSlider->setText("AR"); ui->arSlider->setMaximum(31); ui->drSlider->setText("DR"); ui->drSlider->setMaximum(31); ui->srSlider->setText("SR"); ui->srSlider->setMaximum(31); ui->rrSlider->setText("RR"); ui->rrSlider->setMaximum(15); ui->slSlider->setText("SL"); ui->slSlider->setMaximum(15); ui->tlSlider->setText("TL"); ui->tlSlider->setMaximum(127); ui->ksSlider->setText("KS"); ui->ksSlider->setMaximum(3); ui->mlSlider->setText("ML"); ui->mlSlider->setMaximum(15); ui->dtSlider->setText("DT"); ui->dtSlider->setMaximum(7); sliderMap_ = { { Ui::FMOperatorParameter::AR, ui->arSlider }, { Ui::FMOperatorParameter::DR, ui->drSlider }, { Ui::FMOperatorParameter::SR, ui->srSlider }, { Ui::FMOperatorParameter::RR, ui->rrSlider }, { Ui::FMOperatorParameter::SL, ui->slSlider }, { Ui::FMOperatorParameter::TL, ui->tlSlider }, { Ui::FMOperatorParameter::KS, ui->ksSlider }, { Ui::FMOperatorParameter::ML, ui->mlSlider }, { Ui::FMOperatorParameter::DT, ui->dtSlider } }; for (auto& pair : sliderMap_) { if (pair.second == ui->dtSlider) { QObject::connect(pair.second, &LabeledVerticalSlider::valueChanged, this, [&](int value) { repaintGraph(); if (!isIgnoreEvent_) { if (isDTNegative_) value = DT_UNSIGN_TBL_[value + 3]; emit operatorValueChanged(pair.first, value); } }); } else { QObject::connect(pair.second, &LabeledVerticalSlider::valueChanged, this, [&](int value) { repaintGraph(); if (!isIgnoreEvent_) emit operatorValueChanged(pair.first, value); }); } } ui->ssgegSlider->setEnabled(false); ui->ssgegSlider->setText(tr("Type")); ui->ssgegSlider->setMaximum(7); QObject::connect(ui->ssgegSlider, &LabeledVerticalSlider::valueChanged, this, [&](int value) { repaintGraph(); if (!isIgnoreEvent_) emit operatorValueChanged(Ui::FMOperatorParameter::SSGEG, value); }); ui->envFrame->viewport()->installEventFilter(this); } FMOperatorTable::~FMOperatorTable() { delete ui; } void FMOperatorTable::setEnvelopeSetNames(std::vector list) { envelopeTypes_ = list; } void FMOperatorTable::setColorPalette(std::shared_ptr palette) { palette_ = palette; } void FMOperatorTable::setOperatorNumber(int n) { number_ = n; ui->groupBox->setTitle(tr("Operator %1").arg(n + 1)); } int FMOperatorTable::operatorNumber() const { return number_; } void FMOperatorTable::setValue(Ui::FMOperatorParameter param, int value) { if (param == Ui::FMOperatorParameter::SSGEG) { if (value == -1) { ui->ssgegCheckBox->setChecked(false); } else { ui->ssgegCheckBox->setChecked(true); ui->ssgegSlider->setValue(value); } } else { if (param == Ui::FMOperatorParameter::DT && isDTNegative_) value = DT_SIGN_TBL_[value]; sliderMap_.at(param)->setValue(value); } } void FMOperatorTable::setGroupEnabled(bool enabled) { Ui::EventGuard eg(isIgnoreEvent_); ui->groupBox->setChecked(enabled); } void FMOperatorTable::setDTDisplayType(bool useNegative) { if (isDTNegative_ == useNegative) return; Ui::EventGuard eg(isIgnoreEvent_); isDTNegative_ = useNegative; if (useNegative) { int dt = DT_SIGN_TBL_[ui->dtSlider->value()]; ui->dtSlider->setMinimum(-3); ui->dtSlider->setMaximum(3); ui->dtSlider->setSign(true); ui->dtSlider->setValue(dt); } else { int dt = DT_UNSIGN_TBL_[ui->dtSlider->value() + 3]; ui->dtSlider->setMinimum(0); ui->dtSlider->setMaximum(7); ui->dtSlider->setSign(false); ui->dtSlider->setValue(dt); } } QString FMOperatorTable::toString() const { int dt = ui->dtSlider->value(); if (isDTNegative_) dt = DT_UNSIGN_TBL_[dt + 3]; auto str = QString("%1,%2,%3,%4,%5,%6,%7,%8,%9,%10") .arg(ui->arSlider->value()) .arg(ui->drSlider->value()) .arg(ui->srSlider->value()) .arg(ui->rrSlider->value()) .arg(ui->slSlider->value()) .arg(ui->tlSlider->value()) .arg(ui->ksSlider->value()) .arg(ui->mlSlider->value()) .arg(dt) .arg(ui->ssgegCheckBox->isChecked() ? ui->ssgegSlider->value() : -1); return str; } bool FMOperatorTable::eventFilter(QObject* obj, QEvent* event) { QWidget * viewport = ui->envFrame->viewport(); if (obj == viewport) { if (event->type() == QEvent::Paint) { QPainter painter(viewport); painter.eraseRect(viewport->rect()); painter.drawImage(viewport->rect(), envmap_, envmap_.rect()); // Don't call event->accept(), because isAccepted() is already true. // Return true to stop QAbstractScrollArea from painting(?) on the viewport. return true; } } return false; } void FMOperatorTable::showEvent(QShowEvent*) { resizeGraph(); repaintGraph(); } void FMOperatorTable::resizeEvent(QResizeEvent*) { resizeGraph(); repaintGraph(); } void FMOperatorTable::resizeGraph() { QWidget * viewport = ui->envFrame->viewport(); // Making envmap_ a QImage and using an ARGB pixel format ensures that when we fill // it with instFMEnvBackColor (transparent), the image supports transparency. // On Windows, QPixmap instead defaults to Format_RGB32 which has no alpha channel. envmap_ = QImage(viewport->size(), QImage::Format_ARGB32_Premultiplied); xr_ = (envmap_.width() - (ENV_LINE_W_ + 1) * 2.) / ENV_W_; yr_ = (envmap_.height() - (ENV_LINE_W_ + 1) * 2.) / ENV_H_; } void FMOperatorTable::repaintGraph() { if (!palette_) return; QPointF p0, p1, p2, p3, p4; const double marginHorizon = 100; const double marginAr = 50; const double marginDr = 150; const double marginSr = 400; if (ui->arSlider->value() && ui->tlSlider->value() < 127) { p1.setY(127 - ui->tlSlider->value()); if (ui->arSlider->value() < 31) { double ar = 127. / (marginAr * (31 - ui->arSlider->value()) / 30.); p1.setX(p1.y() / ar); } if (ui->drSlider->value()) { p2.setY(p1.y() * (1. - ui->slSlider->value() / 15.)); if (ui->drSlider->value() == 31) { p2.setX(p1.x()); if (ui->slSlider->value() == 15) { p3 = p2; } else { if (ui->srSlider->value()) { p3.setY(p2.y() / 2.); if (ui->srSlider->value() == 31) { p3.setX(p2.x()); } else { double sr = 127. / (marginSr * (31 - ui->srSlider->value()) / 30.); p3.setX(p2.x() + p3.y() / 2. / sr); } } else { p3 = { p2.x() + ((ui->slSlider->value() == 15) ? 0 : marginHorizon), p2.y() }; } } } else { double dr = 127. / (marginDr * (31 - ui->drSlider->value()) / 30.); p2.setX(p1.x() + (p1.y() - p2.y()) / dr); if (ui->srSlider->value()) { p3.setY(p2.y() / 2.); if (ui->srSlider->value() == 31) { p3.setX(p2.x()); } else { double sr = 127. / (marginSr * (31 - ui->srSlider->value()) / 30.); p3.setX(p2.x() + p3.y() / 2. / sr); } } else { p3 = { p2.x() + ((ui->slSlider->value() == 15) ? 0 : marginHorizon), p2.y() }; } } } else { if (ui->slSlider->value()) { p2 = { p1.x() + marginHorizon, p1.y() }; p3 = p2; } else { p2 = p1; if (ui->srSlider->value()) { p3.setY(p2.y() / 2.); if (ui->srSlider->value() == 31) { p3.setX(p2.x()); } else { double sr = 127. / (marginSr * (31 - ui->srSlider->value()) / 30.); p3.setX(p2.x() + p3.y() / 2. / sr); } } else { p3 = { p2.x() + marginHorizon, p2.y() }; } } } if (!ui->rrSlider->value()) { p4 = { ENV_W_, p3.y() }; } else if (ui->rrSlider->value() == 31) { p4.setX(p3.x()); } else { double rr = 127. / (marginAr * (15 - ui->rrSlider->value()) / 14.); p4.setX(p3.x() + p3.y() / rr); } } double envHeight = ui->ssgegCheckBox->isChecked() ? (ENV_H_ - SSGEG_H_) : ENV_H_; double envHrate = envHeight / 127.; p0.setY((127. - p0.y()) * envHrate); p1.setY((127. - p1.y()) * envHrate); p2.setY((127. - p2.y()) * envHrate); p3.setY((127. - p3.y()) * envHrate); p4.setY((127. - p4.y()) * envHrate); envmap_.fill(palette_->instFMEnvBackColor); QPainter painter(&envmap_); painter.setPen(QPen(palette_->instFMEnvGridColor, ENV_LINE_T_)); drawLine(painter, p1.x(), 0, p1.x(), envHeight); drawLine(painter, p2.x(), 0, p2.x(), envHeight); drawLine(painter, p3.x(), 0, p3.x(), envHeight); painter.setPen(QPen(palette_->instFMEnvLine1Color, ENV_LINE_W_)); drawLines(painter, { p0, p1, p2, p3 }); painter.setPen(QPen(palette_->instFMEnvLine2Color, ENV_LINE_W_)); drawLine(painter, p3, p4); if (ui->ssgegCheckBox->isChecked()) { const double seph = envHeight + 1; painter.setPen(QPen(palette_->instFMEnvBorderColor, ENV_LINE_T_)); drawLine(painter, 0, seph, ENV_W_, seph); const double toph = seph + 2; const double both = ENV_H_; const double horsec = ENV_W_ / 5.; const double dhorsec = horsec * 2; painter.setPen(QPen(palette_->instFMEnvLine3Color, ENV_LINE_W_)); switch (ui->ssgegSlider->value()) { case 0: { for (int i = 0; i < 5; ++i) { drawLine(painter, horsec * i, both, horsec * i, toph); drawLine(painter, horsec * i, toph, horsec * (i + 1), both); } } break; case 1: { drawLine(painter, 0, both, 0, toph); drawLine(painter, 0, toph, horsec, both); drawLine(painter, horsec, both, ENV_W_, both); } break; case 2: { drawLine(painter, 0, both, 0, toph); drawLine(painter, 0, toph, horsec, both); for (int i = 0; i < 2; ++i) { drawLine(painter, horsec + dhorsec * i, both, dhorsec + dhorsec * i, toph); drawLine(painter, dhorsec + dhorsec * i, toph, horsec + dhorsec * (i + 1), both); } } break; case 3: { drawLine(painter, 0, both, 0, toph); drawLine(painter, 0, toph, horsec, both); drawLine(painter, horsec, both, horsec, toph); drawLine(painter, horsec, toph, ENV_W_, toph); } break; case 4: { for (int i = 0; i < 5; ++i) { drawLine(painter, horsec * i, both, horsec * (i + 1), toph); drawLine(painter, horsec * (i + 1), toph, horsec * (i + 1), both); } } break; case 5: { drawLine(painter, 0, both, horsec, toph); drawLine(painter, horsec, toph, ENV_W_, toph); } break; case 6: { for (int i = 0; i < 2; ++i) { drawLine(painter, dhorsec * i, both, horsec + dhorsec * i, toph); drawLine(painter, horsec + dhorsec * i, toph, dhorsec * (i + 1), both); } drawLine(painter, dhorsec * 2, both, ENV_W_, toph); } break; case 7: { drawLine(painter, 0, both, horsec, toph); drawLine(painter, horsec, toph, horsec, both); drawLine(painter, horsec, both, ENV_W_, both); } break; } } ui->envFrame->viewport()->repaint(); } void FMOperatorTable::on_ssgegCheckBox_stateChanged(int) { if (ui->ssgegCheckBox->isChecked()) { ui->ssgegSlider->setEnabled(true); if (!isIgnoreEvent_) emit operatorValueChanged(Ui::FMOperatorParameter::SSGEG, ui->ssgegSlider->value()); } else { ui->ssgegSlider->setEnabled(false); if (!isIgnoreEvent_) emit operatorValueChanged(Ui::FMOperatorParameter::SSGEG, -1); } repaintGraph(); } void FMOperatorTable::on_groupBox_toggled(bool arg1) { if (!isIgnoreEvent_) emit operatorEnableChanged(arg1); } void FMOperatorTable::on_groupBox_customContextMenuRequested(const QPoint &pos) { QPoint globalPos = ui->groupBox->mapToGlobal(pos); QMenu menu; // Leave Before Qt5.7.0 style due to windows xp QAction* copyEnv = menu.addAction(tr("Copy envelope")); QObject::connect(copyEnv, &QAction::triggered, this, [&] { emit copyEnvelopePressed(); }); QAction* pasteEnv = menu.addAction(tr("Paste envelope")); QObject::connect(pasteEnv, &QAction::triggered, this, [&] { emit pasteEnvelopePressed(); }); QMenu* pasteFrom = menu.addMenu(tr("Paste envelope From")); for (size_t i = 0; i < envelopeTypes_.size(); ++i) { QAction* act = pasteFrom->addAction(envelopeTypes_[i]); act->setData(static_cast(i)); } QObject::connect(pasteFrom, &QMenu::triggered, this, [&](QAction* action) { emit pasteEnvelopeFromPressed(action->data().toInt()); }); menu.addSeparator(); QAction* copyOp = menu.addAction(tr("Copy operator")); QObject::connect(copyOp, &QAction::triggered, this, [&] { emit copyOperatorPressed(number_); }); QAction* pasteOp = menu.addAction(tr("Paste operator")); QObject::connect(pasteOp, &QAction::triggered, this, [&] { emit pasteOperatorPressed(number_); }); QClipboard* clipboard = QApplication::clipboard(); pasteEnv->setEnabled(clipboard->text().startsWith("FM_ENVELOPE:")); pasteOp->setEnabled(clipboard->text().startsWith("FM_OPERATOR:")); menu.exec(globalPos); } BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/fm_operator_table.hpp000066400000000000000000000074361476276175200275420ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef FM_OPERATOR_TABLE_HPP #define FM_OPERATOR_TABLE_HPP #include #include #include #include #include #include #include #include #include #include #include "gui/labeled_vertical_slider.hpp" #include "gui/color_palette.hpp" #include "enum_hash.hpp" namespace Ui { class FMOperatorTable; enum class FMOperatorParameter; } class FMOperatorTable : public QFrame { Q_OBJECT public: explicit FMOperatorTable(QWidget *parent = nullptr); ~FMOperatorTable() override; void setEnvelopeSetNames(std::vector list); void setColorPalette(std::shared_ptr palette); void setOperatorNumber(int n); int operatorNumber() const; void setValue(Ui::FMOperatorParameter param, int value); void setGroupEnabled(bool enabled); void setDTDisplayType(bool useNegative); QString toString() const; protected: bool eventFilter(QObject* obj, QEvent* event) override; void showEvent(QShowEvent*) override; void resizeEvent(QResizeEvent*) override; signals: void operatorValueChanged(Ui::FMOperatorParameter param, int value); void operatorEnableChanged(bool enable); void copyEnvelopePressed(); void pasteEnvelopePressed(); void pasteEnvelopeFromPressed(int typenum); void copyOperatorPressed(int num); void pasteOperatorPressed(int num); private slots: void on_ssgegCheckBox_stateChanged(int); void on_groupBox_toggled(bool arg1); void on_groupBox_customContextMenuRequested(const QPoint &pos); private: Ui::FMOperatorTable *ui; std::shared_ptr palette_; int number_; std::unordered_map sliderMap_; std::vector envelopeTypes_; bool isDTNegative_; bool isIgnoreEvent_; // Envelope graph QImage envmap_; static constexpr int ENV_H_ = 127; static constexpr int ENV_W_ = 200; static constexpr int SSGEG_H_ = 35; static constexpr int ENV_LINE_W_ = 2; static constexpr int ENV_LINE_T_ = 1; double xr_, yr_; void resizeGraph(); void repaintGraph(); inline void drawLine(QPainter& painter, qreal x1, qreal y1, qreal x2, qreal y2) { painter.drawLine(ENV_LINE_W_ + x1 * xr_ + 1, ENV_LINE_W_ + y1 * yr_ + 1, ENV_LINE_W_ + x2 * xr_ + 1, ENV_LINE_W_ + y2 * yr_ + 1); } inline void drawLine(QPainter& painter, const QPointF& p1, const QPointF& p2) { drawLine(painter, p1.x(), p1.y(), p2.x(), p2.y()); } inline void drawLines(QPainter& painter, const QVector& ps) { for (int i = 1; i < ps.size(); ++i) { drawLine(painter, ps[i-1], ps[i]); } } }; namespace Ui { enum class FMOperatorParameter { AR, DR, SR, RR, SL, TL, KS, ML, DT, AM, SSGEG }; } #endif // FM_OPERATOR_TABLE_HPP BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/fm_operator_table.ui000066400000000000000000000210231476276175200273540ustar00rootroot00000000000000 FMOperatorTable 0 0 663 241 Frame FMOperatorTable { border: 0; } 3 0 3 0 Operator true 3 3 3 3 3 0 0 0 70 QFrame::StyledPanel QFrame::Raised 0 12 16777215 12 SSGEG Qt::AlignCenter 31 0 QFrame::StyledPanel QFrame::Raised 31 0 QFrame::StyledPanel QFrame::Raised 31 0 QFrame::StyledPanel QFrame::Raised 31 0 QFrame::StyledPanel QFrame::Raised 31 0 QFrame::StyledPanel QFrame::Raised 31 0 QFrame::StyledPanel QFrame::Raised 31 0 QFrame::StyledPanel QFrame::Raised 31 0 QFrame::StyledPanel QFrame::Raised 31 0 QFrame::StyledPanel QFrame::Raised 100 0 QFrame::StyledPanel QFrame::Sunken 1 LabeledVerticalSlider QFrame
gui/labeled_vertical_slider.hpp
1
QAbstractScrollArea QFrame
qabstractscrollarea.h
BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/grid_settings_dialog.cpp000066400000000000000000000030661476276175200302300ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "grid_settings_dialog.hpp" #include "ui_grid_settings_dialog.h" GridSettingsDialog::GridSettingsDialog(int interval, QWidget *parent) : QDialog(parent), ui(new Ui::GridSettingsDialog) { ui->setupUi(this); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); ui->intrSpinBox->setValue(interval); } GridSettingsDialog::~GridSettingsDialog() { delete ui; } int GridSettingsDialog::getInterval() const { return ui->intrSpinBox->value(); } BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/grid_settings_dialog.hpp000066400000000000000000000027711476276175200302370ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef GRID_SETTINGS_DIALOG_HPP #define GRID_SETTINGS_DIALOG_HPP #include namespace Ui { class GridSettingsDialog; } class GridSettingsDialog : public QDialog { Q_OBJECT public: explicit GridSettingsDialog(int interval = 1, QWidget *parent = nullptr); ~GridSettingsDialog() override; int getInterval() const; private: Ui::GridSettingsDialog *ui; }; #endif // GRID_SETTINGS_DIALOG_HPP BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/grid_settings_dialog.ui000066400000000000000000000037321476276175200300630ustar00rootroot00000000000000 GridSettingsDialog 0 0 174 69 Grid Settings Interval 1 2147483647 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() GridSettingsDialog accept() 248 254 157 274 buttonBox rejected() GridSettingsDialog reject() 316 260 286 274 BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/instrument_editor.cpp000066400000000000000000000037541476276175200276260ustar00rootroot00000000000000/* * Copyright (C) 2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "instrument_editor.hpp" #include #include "gui/gui_utils.hpp" InstrumentEditor::InstrumentEditor(int num, QWidget* parent) : QDialog(parent), instNum_(num) { } void InstrumentEditor::setInstrumentNumber(int num) { instNum_ = num; updateWindowTitle(); } void InstrumentEditor::setCore(std::weak_ptr core) { bt_ = core; updateBySettingCore(); } void InstrumentEditor::setConfiguration(std::weak_ptr config) { config_ = config; updateBySettingConfiguration(); } void InstrumentEditor::setColorPalette(std::shared_ptr palette) { palette_ = palette; updateBySettingColorPalette(); } void InstrumentEditor::updateWindowTitle() { if (bt_.expired()) return; auto name = gui_utils::utf8ToQString(bt_.lock()->getInstrument(instNum_)->getName()); setWindowTitle(QString("%1: %2").arg(instNum_, 2, 16, QChar('0')).toUpper().arg(name)); } BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/instrument_editor.hpp000066400000000000000000000066331476276175200276320ustar00rootroot00000000000000/* * Copyright (C) 2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include "bamboo_tracker.hpp" #include "configuration.hpp" #include "jamming.hpp" #include "gui/color_palette.hpp" #include "bamboo_tracker_defs.hpp" enum class InstrumentType: int; /** * @brief The InstrumentEditor class */ class InstrumentEditor : public QDialog { Q_OBJECT public: /** * @brief Return sound source of instrument related to this dialog. * @return Sound source of instrument. */ virtual SoundSource getSoundSource() const = 0; /** * @brief Return instrument type of instrument related to this dialog. * @return Instrument type of instrument. */ virtual InstrumentType getInstrumentType() const = 0; /** * @brief setInstrumentNumber * @param num Instrument number. */ void setInstrumentNumber(int num); /** * @brief getInstrumentNumber * @return Instrument number. */ int getInstrumentNumber() const noexcept { return instNum_; } /** * @brief setCore * @param core Weak pointer to core. */ void setCore(std::weak_ptr core); /** * @brief setConfiguration * @param config Weak pointer to configuration. */ void setConfiguration(std::weak_ptr config); /** * @brief setColorPalette * @param palette */ void setColorPalette(std::shared_ptr palette); /** * @brief updateByConfigurationChange */ virtual void updateByConfigurationChange() = 0; /** * @brief updateWindowTitle */ void updateWindowTitle(); signals: /** * @brief Emitted on jamming key on event. * @param key Jamkey */ void jamKeyOnEvent(JamKey key); /** * @brief Emitted on jamming key off event. * @param key Jamkey */ void jamKeyOffEvent(JamKey key); /** * @brief Emitted when something in dialog is edited. */ void modified(); protected: // Instrument number int instNum_; // Pointer to core std::weak_ptr bt_; // Pointer to color palette std::shared_ptr palette_; // Pointer to configuration std::weak_ptr config_; // Constructor. explicit InstrumentEditor(int num, QWidget* parent = nullptr); // Instrument specific process called in settiing core / cofiguration / color palette. virtual void updateBySettingCore() = 0; virtual void updateBySettingConfiguration() = 0; virtual void updateBySettingColorPalette() = 0; }; BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/instrument_editor_manager.cpp000066400000000000000000000365301476276175200313160ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "instrument_editor_manager.hpp" #include #include #include #include "instrument.hpp" #include "gui/instrument_editor/fm_instrument_editor.hpp" #include "gui/instrument_editor/ssg_instrument_editor.hpp" #include "gui/instrument_editor/adpcm_instrument_editor.hpp" #include "gui/instrument_editor/adpcm_drumkit_editor.hpp" InstrumentEditorManager::InstrumentEditorManager() : typeIndices_ { { InstrumentType::FM, {} }, { InstrumentType::SSG, {} }, { InstrumentType::ADPCM, {} }, { InstrumentType::Drumkit, {} }, } { } InstrumentEditorManager::~InstrumentEditorManager() { removeAll(); } void InstrumentEditorManager::updateByConfiguration() { const auto& cTypeIdcs = typeIndices_; for (const auto& idcs : cTypeIdcs) { for (auto idx : idcs) { if (auto& editor = editors_[idx]) editor->updateByConfigurationChange(); } } } bool InstrumentEditorManager::add(int index, InstrumentEditor* editor) { if (!assertIndex(index)) return false; typeIndices_[editor->getInstrumentType()].insert(index); editors_[index] = editor; // Editor is deleted automatically in closed. editor->setAttribute(Qt::WA_DeleteOnClose); // Parameter numbers update in MainWindow return true; } void InstrumentEditorManager::swap(int index1, int index2) { if (!assertIndex(index1) || !assertIndex(index2)) return; if (editors_[index1]) { InstrumentType typeA = editors_[index1]->getInstrumentType(); typeIndices_[typeA].remove(index1); if (editors_[index2]) { InstrumentType typeB = editors_[index2]->getInstrumentType(); typeIndices_[typeB].remove(index2); std::swap(editors_[index1], editors_[index2]); editors_[index1]->setInstrumentNumber(index1); typeIndices_[typeB].insert(index1); } else { editors_[index2] = editors_[index1]; editors_[index1] = nullptr; } editors_[index2]->setInstrumentNumber(index2); typeIndices_[typeA].insert(index2); } else { if (editors_[index2]) { InstrumentType typeB = editors_[index2]->getInstrumentType(); typeIndices_[typeB].remove(index2); editors_[index1] = editors_[index2]; editors_[index2] = nullptr; editors_[index1]->setInstrumentNumber(index1); typeIndices_[typeB].insert(index1); } } onInstrumentFMEnvelopeNumberChanged(); onInstrumentFMLFONumberChanged(); onInstrumentFMOperatorSequenceNumberChanged(); onInstrumentFMArpeggioNumberChanged(); onInstrumentFMPitchNumberChanged(); onInstrumentSSGWaveformNumberChanged(); onInstrumentSSGEnvelopeNumberChanged(); onInstrumentSSGToneNoiseNumberChanged(); onInstrumentSSGArpeggioNumberChanged(); onInstrumentSSGPitchNumberChanged(); onInstrumentADPCMSampleNumberChanged(); onInstrumentADPCMEnvelopeNumberChanged(); onInstrumentADPCMArpeggioNumberChanged(); onInstrumentADPCMPitchNumberChanged(); onInstrumentADPCMSampleNumberChanged(); } bool InstrumentEditorManager::remove(int index) { if (!assertIndex(index)) return false; if (editors_[index]) { editors_[index]->close(); } onInstrumentFMEnvelopeNumberChanged(); onInstrumentFMLFONumberChanged(); onInstrumentFMOperatorSequenceNumberChanged(); onInstrumentFMArpeggioNumberChanged(); onInstrumentFMPitchNumberChanged(); onInstrumentSSGWaveformNumberChanged(); onInstrumentSSGEnvelopeNumberChanged(); onInstrumentSSGToneNoiseNumberChanged(); onInstrumentSSGArpeggioNumberChanged(); onInstrumentSSGPitchNumberChanged(); onInstrumentADPCMSampleNumberChanged(); onInstrumentADPCMEnvelopeNumberChanged(); onInstrumentADPCMArpeggioNumberChanged(); onInstrumentADPCMPitchNumberChanged(); onInstrumentADPCMSampleNumberChanged(); return true; } void InstrumentEditorManager::showEditor(int index) { if (!assertIndex(index)) return; auto& editor = editors_[index]; if (!editor) return; if (editor->isVisible()) { editor->activateWindow(); } else { editor->show(); } } void InstrumentEditorManager::removeAll() { for (const auto& editor : editors_) { if (!editor) continue; if (!editor->close()) delete editor.data(); } } bool InstrumentEditorManager::hasShownEditor(int index) const { return (assertIndex(index) && !editors_[index].isNull()); } int InstrumentEditorManager::getActivatedEditorIndex() const { const QWidget* win = QApplication::activeWindow(); const auto& cTypeIdcs = typeIndices_; for (const auto& idcs : cTypeIdcs) { for (auto idx : idcs) { if (editors_[idx] == win) return idx; } } return -1; } /********** Slots **********/ void InstrumentEditorManager::onInstrumentNameChanged(int instNum) { if (!assertIndex(instNum)) return; if (auto& editor = editors_[instNum]) { editor->updateWindowTitle(); } } void InstrumentEditorManager::onInstrumentFMEnvelopeParameterChanged(int envNum, int fromInstNum) { const auto idcs = getIndicesForParameterUpdating(InstrumentType::FM, fromInstNum); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onEnvelopeParameterChanged(envNum); } } } void InstrumentEditorManager::onInstrumentFMEnvelopeNumberChanged() { const auto idcs = getIndicesForParameterUpdating(InstrumentType::FM); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onEnvelopeNumberChanged(); } } } void InstrumentEditorManager::onInstrumentFMLFOParameterChanged(int lfoNum, int fromInstNum) { const auto idcs = getIndicesForParameterUpdating(InstrumentType::FM, fromInstNum); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onLFOParameterChanged(lfoNum); } } } void InstrumentEditorManager::onInstrumentFMLFONumberChanged() { const auto idcs = getIndicesForParameterUpdating(InstrumentType::FM); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onLFONumberChanged(); } } } void InstrumentEditorManager::onInstrumentFMOperatorSequenceParameterChanged(FMEnvelopeParameter param, int opSeqNum, int fromInstNum) { const auto idcs = getIndicesForParameterUpdating(InstrumentType::FM, fromInstNum); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onOperatorSequenceParameterChanged(param, opSeqNum); } } } void InstrumentEditorManager::onInstrumentFMOperatorSequenceNumberChanged() { const auto idcs = getIndicesForParameterUpdating(InstrumentType::FM); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onOperatorSequenceNumberChanged(); } } } void InstrumentEditorManager::onInstrumentFMArpeggioParameterChanged(int arpNum, int fromInstNum) { const auto idcs = getIndicesForParameterUpdating(InstrumentType::FM, fromInstNum); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onArpeggioParameterChanged(arpNum); } } } void InstrumentEditorManager::onInstrumentFMArpeggioNumberChanged() { const auto idcs = getIndicesForParameterUpdating(InstrumentType::FM); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onArpeggioNumberChanged(); } } } void InstrumentEditorManager::onInstrumentFMPitchParameterChanged(int ptNum, int fromInstNum) { const auto idcs = getIndicesForParameterUpdating(InstrumentType::FM, fromInstNum); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onPitchParameterChanged(ptNum); } } } void InstrumentEditorManager::onInstrumentFMPitchNumberChanged() { const auto idcs = getIndicesForParameterUpdating(InstrumentType::FM); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onPitchNumberChanged(); } } } void InstrumentEditorManager::onInstrumentSSGWaveformParameterChanged(int wfNum, int fromInstNum) { const auto idcs = getIndicesForParameterUpdating(InstrumentType::SSG, fromInstNum); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onWaveformParameterChanged(wfNum); } } } void InstrumentEditorManager::onInstrumentSSGWaveformNumberChanged() { const auto idcs = getIndicesForParameterUpdating(InstrumentType::SSG); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onWaveformNumberChanged(); } } } void InstrumentEditorManager::onInstrumentSSGToneNoiseParameterChanged(int tnNum, int fromInstNum) { const auto idcs = getIndicesForParameterUpdating(InstrumentType::SSG, fromInstNum); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onToneNoiseParameterChanged(tnNum); } } } void InstrumentEditorManager::onInstrumentSSGToneNoiseNumberChanged() { const auto idcs = getIndicesForParameterUpdating(InstrumentType::SSG); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onToneNoiseNumberChanged(); } } } void InstrumentEditorManager::onInstrumentSSGEnvelopeParameterChanged(int envNum, int fromInstNum) { const auto idcs = getIndicesForParameterUpdating(InstrumentType::SSG, fromInstNum); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onEnvelopeParameterChanged(envNum); } } } void InstrumentEditorManager::onInstrumentSSGEnvelopeNumberChanged() { const auto idcs = getIndicesForParameterUpdating(InstrumentType::SSG); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onEnvelopeNumberChanged(); } } } void InstrumentEditorManager::onInstrumentSSGArpeggioParameterChanged(int arpNum, int fromInstNum) { const auto idcs = getIndicesForParameterUpdating(InstrumentType::SSG, fromInstNum); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onArpeggioParameterChanged(arpNum); } } } void InstrumentEditorManager::onInstrumentSSGArpeggioNumberChanged() { const auto idcs = getIndicesForParameterUpdating(InstrumentType::SSG); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onArpeggioNumberChanged(); } } } void InstrumentEditorManager::onInstrumentSSGPitchParameterChanged(int ptNum, int fromInstNum) { const auto idcs = getIndicesForParameterUpdating(InstrumentType::SSG, fromInstNum); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onPitchParameterChanged(ptNum); } } } void InstrumentEditorManager::onInstrumentSSGPitchNumberChanged() { const auto idcs = getIndicesForParameterUpdating(InstrumentType::SSG); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onPitchNumberChanged(); } } } void InstrumentEditorManager::onInstrumentADPCMSampleParameterChanged(int sampNum, int fromInstNum) { Q_UNUSED(fromInstNum) // Update all adpcm editor to change sample memory viewer and sample viewer const auto idcsAdpcm = getIndicesForParameterUpdating(InstrumentType::ADPCM); for (auto idx : idcsAdpcm) { if (auto editor = qobject_cast(editors_[idx])) { editor->onSampleParameterChanged(sampNum); } } const auto idcsKit = getIndicesForParameterUpdating(InstrumentType::Drumkit); for (auto idx : idcsKit) { if (auto editor = qobject_cast(editors_[idx])) { editor->onSampleParameterChanged(sampNum); } } } void InstrumentEditorManager::onInstrumentADPCMSampleNumberChanged() { const auto idcsAdpcm = getIndicesForParameterUpdating(InstrumentType::ADPCM); for (auto idx : idcsAdpcm) { if (auto editor = qobject_cast(editors_[idx])) { editor->onSampleNumberChanged(); } } const auto idcsKit = getIndicesForParameterUpdating(InstrumentType::Drumkit); for (auto idx : idcsKit) { if (auto editor = qobject_cast(editors_[idx])) { editor->onSampleNumberChanged(); } } } void InstrumentEditorManager::onInstrumentADPCMSampleMemoryUpdated() { const auto idcsAdpcm = getIndicesForParameterUpdating(InstrumentType::ADPCM); for (auto idx : idcsAdpcm) { if (auto editor = qobject_cast(editors_[idx])) { editor->onSampleMemoryUpdated(); } } const auto idcsKit = getIndicesForParameterUpdating(InstrumentType::Drumkit); for (auto idx : idcsKit) { if (auto editor = qobject_cast(editors_[idx])) { editor->onSampleMemoryUpdated(); } } } void InstrumentEditorManager::onInstrumentADPCMEnvelopeParameterChanged(int envNum, int fromInstNum) { const auto idcs = getIndicesForParameterUpdating(InstrumentType::ADPCM, fromInstNum); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onEnvelopeParameterChanged(envNum); } } } void InstrumentEditorManager::onInstrumentADPCMEnvelopeNumberChanged() { const auto idcs = getIndicesForParameterUpdating(InstrumentType::ADPCM); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onEnvelopeNumberChanged(); } } } void InstrumentEditorManager::onInstrumentADPCMArpeggioParameterChanged(int arpNum, int fromInstNum) { const auto idcs = getIndicesForParameterUpdating(InstrumentType::ADPCM, fromInstNum); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onArpeggioParameterChanged(arpNum); } } } void InstrumentEditorManager::onInstrumentADPCMArpeggioNumberChanged() { const auto idcs = getIndicesForParameterUpdating(InstrumentType::ADPCM); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onArpeggioNumberChanged(); } } } void InstrumentEditorManager::onInstrumentADPCMPitchParameterChanged(int ptNum, int fromInstNum) { const auto idcs = getIndicesForParameterUpdating(InstrumentType::ADPCM, fromInstNum); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onPitchParameterChanged(ptNum); } } } void InstrumentEditorManager::onInstrumentADPCMPitchNumberChanged() { const auto idcs = getIndicesForParameterUpdating(InstrumentType::ADPCM); for (auto idx : idcs) { if (auto editor = qobject_cast(editors_[idx])) { editor->onPitchNumberChanged(); } } } BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/instrument_editor_manager.hpp000066400000000000000000000131161476276175200313160ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include #include "instrument/envelope_fm.hpp" #include "bamboo_tracker_defs.hpp" #include "utils.hpp" class InstrumentEditor; enum class InstrumentType; // Hash function for QHash constexpr uint qHash(const InstrumentType& type, uint seed) { return qHash(static_cast(type), seed); } /** * @brief The InstrumentEditorManager class */ class InstrumentEditorManager : public QObject { Q_OBJECT public: /** * @brief Constructor. */ InstrumentEditorManager(); /** * @brief Destructor. */ ~InstrumentEditorManager(); /** * @brief Update editors by changing configuration. */ void updateByConfiguration(); /** * @brief add Add instrument editor to editor list. * @param index Index of instrument editor in editor list. * @param editor Shared pointer of editor. * @return \c true when editor addition is success. */ bool add(int index, InstrumentEditor* editor); /** * @brief Swap editors in editor list. * @param index1 1st index of editor. * @param index2 2nd index of editor. */ void swap(int index1, int index2); /** * @brief Remove editor from editor list and dispose it. * If editor is opened, it will be closed. * @param index Index of editor in editor list. * @return \c true when editor deletion is success. */ bool remove(int index); /** * @brief showEditor * @param index Index of editor in editor list. */ void showEditor(int index); /** * @brief Dispose all managed editors. * Opened editors are closed. */ void removeAll(); /** * @brief hasShownEditor * @param index Index of editor in editor list. * @return \c true when editor list registers editor. */ bool hasShownEditor(int index) const; /** * @brief getActivatedEditorIndex * @return Index of editor in doalog list. * When no editor is activated, returns -1. */ int getActivatedEditorIndex() const; public slots: // Update windows title void onInstrumentNameChanged(int instNum); // Update FM parameters void onInstrumentFMEnvelopeParameterChanged(int envNum, int fromInstNum); void onInstrumentFMEnvelopeNumberChanged(); void onInstrumentFMLFOParameterChanged(int lfoNum, int fromInstNum); void onInstrumentFMLFONumberChanged(); void onInstrumentFMOperatorSequenceParameterChanged(FMEnvelopeParameter param, int opSeqNum, int fromInstNum); void onInstrumentFMOperatorSequenceNumberChanged(); void onInstrumentFMArpeggioParameterChanged(int arpNum, int fromInstNum); void onInstrumentFMArpeggioNumberChanged(); void onInstrumentFMPitchParameterChanged(int ptNum, int fromInstNum); void onInstrumentFMPitchNumberChanged(); // Update SSG parameters void onInstrumentSSGWaveformParameterChanged(int wfNum, int fromInstNum); void onInstrumentSSGWaveformNumberChanged(); void onInstrumentSSGToneNoiseParameterChanged(int tnNum, int fromInstNum); void onInstrumentSSGToneNoiseNumberChanged(); void onInstrumentSSGEnvelopeParameterChanged(int envNum, int fromInstNum); void onInstrumentSSGEnvelopeNumberChanged(); void onInstrumentSSGArpeggioParameterChanged(int arpNum, int fromInstNum); void onInstrumentSSGArpeggioNumberChanged(); void onInstrumentSSGPitchParameterChanged(int ptNum, int fromInstNum); void onInstrumentSSGPitchNumberChanged(); // Update ADPCM/Drumkit parameters void onInstrumentADPCMSampleParameterChanged(int sampNum, int fromInstNum); void onInstrumentADPCMSampleNumberChanged(); void onInstrumentADPCMSampleMemoryUpdated(); void onInstrumentADPCMEnvelopeParameterChanged(int envNum, int fromInstNum); void onInstrumentADPCMEnvelopeNumberChanged(); void onInstrumentADPCMArpeggioParameterChanged(int arpNum, int fromInstNum); void onInstrumentADPCMArpeggioNumberChanged(); void onInstrumentADPCMPitchParameterChanged(int ptNum, int fromInstNum); void onInstrumentADPCMPitchNumberChanged(); private: // Size of editor list enum { MAX_NUM_EDITOR = 128 }; // Editor list QPointer editors_[MAX_NUM_EDITOR]; // Map of instrument type and editor index list QHash> typeIndices_; // Assert that given index is valid for editor list. static constexpr bool assertIndex(int index) { return utils::isInRange(index, 0, MAX_NUM_EDITOR); } // Get indices of editor which is neccessary to update parameters. inline QSet getIndicesForParameterUpdating(InstrumentType type, int skippedIdx = -1) { auto idcs = typeIndices_[type]; if (assertIndex(skippedIdx)) idcs.remove(skippedIdx); return idcs; } }; BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/instrument_editor_utils.cpp000066400000000000000000000027161476276175200310430ustar00rootroot00000000000000/* * Copyright (C) 2020-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "instrument_editor_utils.hpp" #include #include namespace inst_edit_utils { QString generateUsersString(const std::multiset& users) { QStringList l; std::transform(users.begin(), users.end(), std::back_inserter(l), [](int n) { return QString("%1").arg(n, 2, 16, QChar('0')); }); return l.join(",").toUpper(); } } BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/instrument_editor_utils.hpp000066400000000000000000000025251476276175200310460ustar00rootroot00000000000000/* * Copyright (C) 2020-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef INSTRUMENT_EDITOR_UTILS_HPP #define INSTRUMENT_EDITOR_UTILS_HPP #include #include namespace inst_edit_utils { QString generateUsersString(const std::multiset& users); } #endif // INSTRUMENT_EDITOR_UTILS_HPP BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/pan_macro_editor.cpp000066400000000000000000000070211476276175200273440ustar00rootroot00000000000000/* * Copyright (C) 2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "pan_macro_editor.hpp" #include namespace { const QString MML_DISP[4] = { "n", "r", "l", "c" }; } PanMacroEditor::PanMacroEditor(QWidget* parent) : VisualizedInstrumentMacroEditor(parent) { setMaximumDisplayedRowCount(2); setDefaultRow(3); AddRow(tr("Right"), false); AddRow(tr("Left"), false); autoFitLabelWidth(); } PanMacroEditor::~PanMacroEditor() = default; void PanMacroEditor::drawField() { QPainter painter(&pixmap_); painter.setFont(font_); int textOffset = fontAscend_ - fontHeight_ + fontLeading_ / 2; // Row label painter.setPen(palette_->instSeqTagColor); painter.drawText(1, rowHeights_[0] + textOffset, labels_[1]); painter.drawText(1, rowHeights_[0] + rowHeights_[1] + textOffset, labels_[0]); for (size_t i = 1; i < cols_.size(); i += 2) { painter.fillRect(labWidth_ + std::accumulate(colWidths_.begin(), colWidths_.begin() + static_cast(i), 0), 0, colWidths_[i], fieldHeight_, palette_->instSeqOddColColor); } // Sequence painter.setPen(palette_->instSeqCellTextColor); for (size_t i = 0; i < cols_.size(); ++i) { int v = cols_[i].row; int x = labWidth_ + std::accumulate(colWidths_.begin(), colWidths_.begin() + static_cast(i), 0); if (v & 0b10) { // Left painter.fillRect(x, 0, colWidths_[i], rowHeights_[0], palette_->instSeqCellColor); } if (v & 0b01) { // Right painter.fillRect(x, rowHeights_[0], colWidths_[i], rowHeights_[1], palette_->instSeqCellColor); } } if (hovCol_ >= 0 && hovRow_ >= 0) { painter.fillRect(labWidth_ + std::accumulate(colWidths_.begin(), colWidths_.begin() + hovCol_, 0), std::accumulate(rowHeights_.begin(), rowHeights_.begin() + hovRow_, 0), colWidths_[static_cast(hovCol_)], rowHeights_[static_cast(hovRow_)], palette_->instSeqHovColor); } } int PanMacroEditor::detectRowNumberForMouseEvent(int col, int internalRow) const { int lrFlag = internalRow ? 0b01 /* Right */ : 0b10; /* Left */ return cols_.at(static_cast(col)).row ^ lrFlag; } int PanMacroEditor::maxInMML() const { return 3; } QString PanMacroEditor::convertSequenceDataUnitToMML(Column col) { return MML_DISP[col.row]; } bool PanMacroEditor::interpretDataInMML(QString& text, int& cnt, std::vector& column) { auto c = text.left(1); for (int i = 0; i < 4; ++i) { if (c == MML_DISP[i]) { column.push_back({ i, -1, "" }); ++cnt; text.remove(0, 1); return true; } } return false; } BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/pan_macro_editor.hpp000066400000000000000000000033071476276175200273540ustar00rootroot00000000000000/* * Copyright (C) 2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef PAN_MACRO_EDITOR_HPP #define PAN_MACRO_EDITOR_HPP #include "visualized_instrument_macro_editor.hpp" class PanMacroEditor final : public VisualizedInstrumentMacroEditor { Q_OBJECT public: explicit PanMacroEditor(QWidget* parent = nullptr); ~PanMacroEditor() override; protected: void drawField() override; int detectRowNumberForMouseEvent(int col, int internalRow) const override; int maxInMML() const override; QString convertSequenceDataUnitToMML(Column col) override; bool interpretDataInMML(QString& text, int& cnt, std::vector& column) override; }; #endif // PAN_MACRO_EDITOR_HPP BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/sample_length_dialog.cpp000066400000000000000000000030421476276175200301770ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "sample_length_dialog.hpp" #include "ui_sample_length_dialog.h" SampleLengthDialog::SampleLengthDialog(int len, QWidget *parent) : QDialog(parent), ui(new Ui::SampleLengthDialog) { ui->setupUi(this); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); ui->spinBox->setValue(len); } SampleLengthDialog::~SampleLengthDialog() { delete ui; } int SampleLengthDialog::getLength() const { return ui->spinBox->value(); } BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/sample_length_dialog.hpp000066400000000000000000000027561476276175200302170ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef SAMPLE_LENGTH_DIALOG_HPP #define SAMPLE_LENGTH_DIALOG_HPP #include namespace Ui { class SampleLengthDialog; } class SampleLengthDialog : public QDialog { Q_OBJECT public: explicit SampleLengthDialog(int len, QWidget *parent = nullptr); ~SampleLengthDialog() override; int getLength() const; private: Ui::SampleLengthDialog *ui; }; #endif // SAMPLE_LENGTH_DIALOG_HPP BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/sample_length_dialog.ui000066400000000000000000000040331476276175200300330ustar00rootroot00000000000000 SampleLengthDialog 0 0 174 69 Resize Length 2 2147483646 2 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() SampleLengthDialog accept() 248 254 157 274 buttonBox rejected() SampleLengthDialog reject() 316 260 286 274 BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/ssg_instrument_editor.cpp000066400000000000000000001010141476276175200304660ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "ssg_instrument_editor.hpp" #include "ui_ssg_instrument_editor.h" #include #include #include #include #include "gui/event_guard.hpp" #include "note.hpp" #include "sequence_property.hpp" #include "gui/jam_layout.hpp" #include "gui/instrument_editor/instrument_editor_utils.hpp" namespace { bool isModulatedWaveformSSG(int type) { switch (type) { case SSGWaveformType::SQUARE: case SSGWaveformType::TRIANGLE: case SSGWaveformType::SAW: case SSGWaveformType::INVSAW: return false; case SSGWaveformType::SQM_TRIANGLE: case SSGWaveformType::SQM_SAW: case SSGWaveformType::SQM_INVSAW: return true; default: throw std::invalid_argument("Invalid SSGWaveformType"); } } } SsgInstrumentEditor::SsgInstrumentEditor(int num, QWidget* parent) : InstrumentEditor(num, parent), ui(new Ui::SsgInstrumentEditor) { ui->setupUi(this); //========== Waveform ==========// ui->waveEditor->setMaximumDisplayedRowCount(7); ui->waveEditor->setDefaultRow(0); ui->waveEditor->AddRow(tr("Sq"), false); ui->waveEditor->AddRow(tr("Tri"), false); ui->waveEditor->AddRow(tr("Saw"), false); ui->waveEditor->AddRow(tr("InvSaw"), false); ui->waveEditor->AddRow(tr("SMTri"), false); ui->waveEditor->AddRow(tr("SMSaw"), false); ui->waveEditor->AddRow(tr("SMInvSaw"), false); ui->waveEditor->autoFitLabelWidth(); QObject::connect(ui->waveEditor, &VisualizedInstrumentMacroEditor::sequenceDataAdded, this, [&](int row, int col) { if (!isIgnoreEvent_) { if (isModulatedWaveformSSG(row)) { SSGWaveformUnit data = setWaveformSequenceColumn(col, row); // Set square-mask frequency bt_.lock()->addWaveformSSGSequenceData(ui->waveNumSpinBox->value(), data); } else { bt_.lock()->addWaveformSSGSequenceData(ui->waveNumSpinBox->value(), SSGWaveformUnit::makeOnlyDataUnit(row)); } emit waveformParameterChanged(ui->waveNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->waveEditor, &VisualizedInstrumentMacroEditor::sequenceDataRemoved, this, [&] { if (!isIgnoreEvent_) { bt_.lock()->removeWaveformSSGSequenceData(ui->waveNumSpinBox->value()); emit waveformParameterChanged(ui->waveNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->waveEditor, &VisualizedInstrumentMacroEditor::sequenceDataChanged, this, [&](int row, int col) { if (!isIgnoreEvent_) { if (isModulatedWaveformSSG(row)) { SSGWaveformUnit data = setWaveformSequenceColumn(col, row); // Set square-mask frequency bt_.lock()->setWaveformSSGSequenceData(ui->waveNumSpinBox->value(), col, data); } else { bt_.lock()->setWaveformSSGSequenceData(ui->waveNumSpinBox->value(), col, SSGWaveformUnit::makeOnlyDataUnit(row)); } emit waveformParameterChanged(ui->waveNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->waveEditor, &VisualizedInstrumentMacroEditor::loopAdded, this, [&](InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->addWaveformSSGLoop(ui->waveNumSpinBox->value(), loop); emit waveformParameterChanged(ui->waveNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->waveEditor, &VisualizedInstrumentMacroEditor::loopRemoved, this, [&](int begin, int end) { if (!isIgnoreEvent_) { bt_.lock()->removeWaveformSSGLoop(ui->waveNumSpinBox->value(), begin, end); emit waveformParameterChanged(ui->waveNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->waveEditor, &VisualizedInstrumentMacroEditor::loopCleared, this, [&] { if (!isIgnoreEvent_) { bt_.lock()->clearWaveformSSGLoops(ui->waveNumSpinBox->value()); emit waveformParameterChanged(ui->waveNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->waveEditor, &VisualizedInstrumentMacroEditor::loopChanged, this, [&](int prevBegin, int prevEnd, InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->changeWaveformSSGLoop(ui->waveNumSpinBox->value(), prevBegin, prevEnd, loop); emit waveformParameterChanged(ui->waveNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->waveEditor, &VisualizedInstrumentMacroEditor::releaseChanged, this, [&](InstrumentSequenceRelease release) { if (!isIgnoreEvent_) { bt_.lock()->setWaveformSSGRelease(ui->waveNumSpinBox->value(), release); emit waveformParameterChanged(ui->waveNumSpinBox->value(), instNum_); emit modified(); } }); //========== Tone/Noise ==========// QObject::connect(ui->tnEditor, &VisualizedInstrumentMacroEditor::sequenceDataAdded, this, [&](int row, int) { if (!isIgnoreEvent_) { bt_.lock()->addToneNoiseSSGSequenceData(ui->tnNumSpinBox->value(), row); emit toneNoiseParameterChanged(ui->tnNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->tnEditor, &VisualizedInstrumentMacroEditor::sequenceDataRemoved, this, [&] { if (!isIgnoreEvent_) { bt_.lock()->removeToneNoiseSSGSequenceData(ui->tnNumSpinBox->value()); emit toneNoiseParameterChanged(ui->tnNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->tnEditor, &VisualizedInstrumentMacroEditor::sequenceDataChanged, this, [&](int row, int col) { if (!isIgnoreEvent_) { bt_.lock()->setToneNoiseSSGSequenceData(ui->tnNumSpinBox->value(), col, row); emit toneNoiseParameterChanged(ui->tnNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->tnEditor, &VisualizedInstrumentMacroEditor::loopAdded, this, [&](InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->addToneNoiseSSGLoop(ui->tnNumSpinBox->value(), loop); emit toneNoiseParameterChanged(ui->tnNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->tnEditor, &VisualizedInstrumentMacroEditor::loopRemoved, this, [&](int begin, int end) { if (!isIgnoreEvent_) { bt_.lock()->removeToneNoiseSSGLoop(ui->tnNumSpinBox->value(), begin, end); emit toneNoiseParameterChanged(ui->tnNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->tnEditor, &VisualizedInstrumentMacroEditor::loopCleared, this, [&] { if (!isIgnoreEvent_) { bt_.lock()->clearToneNoiseSSGLoops(ui->tnNumSpinBox->value()); emit toneNoiseParameterChanged(ui->tnNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->tnEditor, &VisualizedInstrumentMacroEditor::loopChanged, this, [&](int prevBegin, int prevEnd, InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->changeToneNoiseSSGLoop(ui->tnNumSpinBox->value(), prevBegin, prevEnd, loop); emit toneNoiseParameterChanged(ui->tnNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->tnEditor, &VisualizedInstrumentMacroEditor::releaseChanged, this, [&](InstrumentSequenceRelease release) { if (!isIgnoreEvent_) { bt_.lock()->setToneNoiseSSGRelease(ui->tnNumSpinBox->value(), release); emit toneNoiseParameterChanged(ui->tnNumSpinBox->value(), instNum_); emit modified(); } }); //========== Envelope ==========// ui->envEditor->setMaximumDisplayedRowCount(16); ui->envEditor->setDefaultRow(15); for (int i = 0; i < 16; ++i) { ui->envEditor->AddRow(QString::number(i), false); } for (int i = 0; i < 8; ++i) { ui->envEditor->AddRow(tr("HEnv %1").arg(i), false); } ui->envEditor->autoFitLabelWidth(); ui->envEditor->setMultipleReleaseState(true); ui->envEditor->setPermittedReleaseTypes( VisualizedInstrumentMacroEditor::PermittedReleaseFlag::ABSOLUTE_RELEASE | VisualizedInstrumentMacroEditor::PermittedReleaseFlag::RELATIVE_RELEASE | VisualizedInstrumentMacroEditor::PermittedReleaseFlag::FIXED_RELEASE); QObject::connect(ui->envEditor, &VisualizedInstrumentMacroEditor::sequenceDataAdded, this, [&](int row, int col) { if (!isIgnoreEvent_) { if (row >= 16) { SSGEnvelopeUnit data = setEnvelopeSequenceColumn(col, row); // Set hard frequency bt_.lock()->addEnvelopeSSGSequenceData(ui->envNumSpinBox->value(), data); } else { bt_.lock()->addEnvelopeSSGSequenceData(ui->envNumSpinBox->value(), SSGEnvelopeUnit::makeOnlyDataUnit(row)); } emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->envEditor, &VisualizedInstrumentMacroEditor::sequenceDataRemoved, this, [&] { if (!isIgnoreEvent_) { bt_.lock()->removeEnvelopeSSGSequenceData(ui->envNumSpinBox->value()); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->envEditor, &VisualizedInstrumentMacroEditor::sequenceDataChanged, this, [&](int row, int col) { if (!isIgnoreEvent_) { if (row >= 16) { SSGEnvelopeUnit data = setEnvelopeSequenceColumn(col, row); // Set hard frequency bt_.lock()->setEnvelopeSSGSequenceData(ui->envNumSpinBox->value(), col, data); } else { bt_.lock()->setEnvelopeSSGSequenceData(ui->envNumSpinBox->value(), col, SSGEnvelopeUnit::makeOnlyDataUnit(row)); } emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->envEditor, &VisualizedInstrumentMacroEditor::loopAdded, this, [&](InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->addEnvelopeSSGLoop(ui->envNumSpinBox->value(), loop); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->envEditor, &VisualizedInstrumentMacroEditor::loopRemoved, this, [&](int begin, int end) { if (!isIgnoreEvent_) { bt_.lock()->removeEnvelopeSSGLoop(ui->envNumSpinBox->value(), begin, end); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->envEditor, &VisualizedInstrumentMacroEditor::loopCleared, this, [&] { if (!isIgnoreEvent_) { bt_.lock()->clearEnvelopeSSGLoops(ui->envNumSpinBox->value()); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->envEditor, &VisualizedInstrumentMacroEditor::loopChanged, this, [&](int prevBegin, int prevEnd, InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->changeEnvelopeSSGLoop(ui->envNumSpinBox->value(), prevBegin, prevEnd, loop); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->envEditor, &VisualizedInstrumentMacroEditor::releaseChanged, this, [&](InstrumentSequenceRelease release) { if (!isIgnoreEvent_) { bt_.lock()->setEnvelopeSSGRelease(ui->envNumSpinBox->value(), release); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); //========== Arpeggio ==========// ui->arpTypeComboBox->addItem(tr("Absolute"), static_cast(SequenceType::AbsoluteSequence)); ui->arpTypeComboBox->addItem(tr("Fixed"), static_cast(SequenceType::FixedSequence)); ui->arpTypeComboBox->addItem(tr("Relative"), static_cast(SequenceType::RelativeSequence)); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::sequenceDataAdded, this, [&](int row, int) { if (!isIgnoreEvent_) { bt_.lock()->addArpeggioSSGSequenceData(ui->arpNumSpinBox->value(), row); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::sequenceDataRemoved, this, [&] { if (!isIgnoreEvent_) { bt_.lock()->removeArpeggioSSGSequenceData(ui->arpNumSpinBox->value()); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::sequenceDataChanged, this, [&](int row, int col) { if (!isIgnoreEvent_) { bt_.lock()->setArpeggioSSGSequenceData(ui->arpNumSpinBox->value(), col, row); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::loopAdded, this, [&](InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->addArpeggioSSGLoop(ui->arpNumSpinBox->value(), loop); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::loopRemoved, this, [&](int begin, int end) { if (!isIgnoreEvent_) { bt_.lock()->removeArpeggioSSGLoop(ui->arpNumSpinBox->value(), begin, end); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::loopCleared, this, [&] { if (!isIgnoreEvent_) { bt_.lock()->clearArpeggioSSGLoops(ui->arpNumSpinBox->value()); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::loopChanged, this, [&](int prevBegin, int prevEnd, InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->changeArpeggioSSGLoop(ui->arpNumSpinBox->value(), prevBegin, prevEnd, loop); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::releaseChanged, this, [&](InstrumentSequenceRelease release) { if (!isIgnoreEvent_) { bt_.lock()->setArpeggioSSGRelease(ui->arpNumSpinBox->value(), release); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->arpTypeComboBox, static_cast(&QComboBox::currentIndexChanged), this, &SsgInstrumentEditor::onArpeggioTypeChanged); //========== Pitch ==========// ui->ptEditor->setMaximumDisplayedRowCount(15); ui->ptEditor->setDefaultRow(127); ui->ptEditor->setLabelDiaplayMode(true); for (int i = 0; i < 255; ++i) { ui->ptEditor->AddRow(QString::asprintf("%+d", i - 127), false); } ui->ptEditor->autoFitLabelWidth(); ui->ptEditor->setUpperRow(134); ui->ptEditor->setMMLDisplay0As(-127); ui->ptTypeComboBox->addItem(tr("Absolute"), static_cast(SequenceType::AbsoluteSequence)); ui->ptTypeComboBox->addItem(tr("Relative"), static_cast(SequenceType::RelativeSequence)); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::sequenceDataAdded, this, [&](int row, int) { if (!isIgnoreEvent_) { bt_.lock()->addPitchSSGSequenceData(ui->ptNumSpinBox->value(), row); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::sequenceDataRemoved, this, [&] { if (!isIgnoreEvent_) { bt_.lock()->removePitchSSGSequenceData(ui->ptNumSpinBox->value()); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::sequenceDataChanged, this, [&](int row, int col) { if (!isIgnoreEvent_) { bt_.lock()->setPitchSSGSequenceData(ui->ptNumSpinBox->value(), col, row); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::loopAdded, this, [&](InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->addPitchSSGLoop(ui->ptNumSpinBox->value(), loop); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::loopRemoved, this, [&](int begin, int end) { if (!isIgnoreEvent_) { bt_.lock()->removePitchSSGLoop(ui->ptNumSpinBox->value(), begin, end); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::loopCleared, this, [&] { if (!isIgnoreEvent_) { bt_.lock()->clearPitchSSGLoops(ui->ptNumSpinBox->value()); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::loopChanged, this, [&](int prevBegin, int prevEnd, InstrumentSequenceLoop loop) { if (!isIgnoreEvent_) { bt_.lock()->changePitchSSGLoop(ui->ptNumSpinBox->value(), prevBegin, prevEnd, loop); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::releaseChanged, this, [&](InstrumentSequenceRelease release) { if (!isIgnoreEvent_) { bt_.lock()->setPitchSSGRelease(ui->ptNumSpinBox->value(), release); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->ptTypeComboBox, static_cast(&QComboBox::currentIndexChanged), this, &SsgInstrumentEditor::onPitchTypeChanged); } SsgInstrumentEditor::~SsgInstrumentEditor() { delete ui; } SoundSource SsgInstrumentEditor::getSoundSource() const { return SoundSource::SSG; } InstrumentType SsgInstrumentEditor::getInstrumentType() const { return InstrumentType::SSG; } void SsgInstrumentEditor::updateByConfigurationChange() { ui->arpEditor->onNoteNamesUpdated(); } void SsgInstrumentEditor::updateBySettingCore() { updateInstrumentParameters(); } void SsgInstrumentEditor::updateBySettingColorPalette() { ui->waveEditor->setColorPalette(palette_); ui->tnEditor->setColorPalette(palette_); ui->envEditor->setColorPalette(palette_); ui->arpEditor->setColorPalette(palette_); ui->ptEditor->setColorPalette(palette_); } void SsgInstrumentEditor::updateInstrumentParameters() { Ui::EventGuard eg(isIgnoreEvent_); updateWindowTitle(); setInstrumentWaveformParameters(); setInstrumentToneNoiseParameters(); setInstrumentEnvelopeParameters(); setInstrumentArpeggioParameters(); setInstrumentPitchParameters(); } /********** Events **********/ // MUST DIRECT CONNECTION void SsgInstrumentEditor::keyPressEvent(QKeyEvent *event) { // General keys switch (event->key()) { case Qt::Key_Escape: close(); break; default: // For jam key on if (!event->isAutoRepeat()) { // Musical keyboard Qt::Key qtKey = static_cast(event->key()); try { JamKey jk = getJamKeyFromLayoutMapping(qtKey, config_); emit jamKeyOnEvent(jk); } catch (std::invalid_argument&) {} } break; } } // MUST DIRECT CONNECTION void SsgInstrumentEditor::keyReleaseEvent(QKeyEvent *event) { // For jam key off if (!event->isAutoRepeat()) { Qt::Key qtKey = static_cast(event->key()); try { JamKey jk = getJamKeyFromLayoutMapping(qtKey, config_); emit jamKeyOffEvent(jk); } catch (std::invalid_argument&) {} } } //--- Waveform void SsgInstrumentEditor::setInstrumentWaveformParameters() { Ui::EventGuard ev(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instSSG = dynamic_cast(inst.get()); ui->waveNumSpinBox->setValue(instSSG->getWaveformNumber()); ui->waveEditor->clearData(); for (auto& unit : instSSG->getWaveformSequence()) { QString str(""); if (isModulatedWaveformSSG(unit.data)) { if (unit.type == SSGWaveformUnit::RatioSubdata) { int r1, r2; unit.getSubdataAsRatio(r1, r2); str = QString("%1/%2").arg(r1).arg(r2); } else { str = QString::number(unit.subdata); } } ui->waveEditor->addSequenceData(unit.data, str, unit.subdata); } for (auto& loop : instSSG->getWaveformLoopRoot().getAllLoops()) { ui->waveEditor->addLoop(loop.getBeginPos(), loop.getEndPos(), loop.getTimes()); } ui->waveEditor->setRelease(instSSG->getWaveformRelease()); if (instSSG->getWaveformEnabled()) { ui->waveEditGroupBox->setChecked(true); onWaveformNumberChanged(); } else { ui->waveEditGroupBox->setChecked(false); } } SSGWaveformUnit SsgInstrumentEditor::setWaveformSequenceColumn(int col, int wfRow) { auto button = ui->squareMaskButtonGroup->checkedButton(); if (button == ui->squareMaskRawRadioButton) { SSGWaveformUnit unit = SSGWaveformUnit::makeRawUnit(wfRow, ui->squareMaskRawSpinBox->value()); ui->waveEditor->setText(col, QString::number(unit.subdata)); ui->waveEditor->setSubdata(col, unit.subdata); return unit; } else { SSGWaveformUnit unit = SSGWaveformUnit::makeRatioUnit(wfRow, ui->squareMaskToneSpinBox->value(), ui->squareMaskMaskSpinBox->value()); ui->waveEditor->setText(col, QString::number(ui->squareMaskToneSpinBox->value()) + "/" + QString::number(ui->squareMaskMaskSpinBox->value())); ui->waveEditor->setSubdata(col, unit.subdata); return unit; } } /********** Slots **********/ void SsgInstrumentEditor::onWaveformNumberChanged() { // Change users view std::multiset users = bt_.lock()->getWaveformSSGUsers(ui->waveNumSpinBox->value()); ui->waveUsersLineEdit->setText(inst_edit_utils::generateUsersString(users)); } void SsgInstrumentEditor::onWaveformParameterChanged(int wfNum) { if (ui->waveNumSpinBox->value() == wfNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentWaveformParameters(); } } void SsgInstrumentEditor::on_waveEditGroupBox_toggled(bool arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentSSGWaveformEnabled(instNum_, arg1); setInstrumentWaveformParameters(); emit waveformNumberChanged(); emit modified(); } onWaveformNumberChanged(); } void SsgInstrumentEditor::on_waveNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentSSGWaveform(instNum_, arg1); setInstrumentWaveformParameters(); emit waveformNumberChanged(); emit modified(); } onWaveformNumberChanged(); } void SsgInstrumentEditor::on_squareMaskRawSpinBox_valueChanged(int arg1) { ui->squareMaskRawSpinBox->setSuffix( QString(" (0x") + QString("%1 | ").arg(arg1, 3, 16, QChar('0')).toUpper() + QString("%1Hz)").arg(arg1 ? QString::number(124800.0 / arg1, 'f', 4) : "-") ); } //--- Tone/Noise void SsgInstrumentEditor::setInstrumentToneNoiseParameters() { Ui::EventGuard ev(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instSSG = dynamic_cast(inst.get()); ui->tnNumSpinBox->setValue(instSSG->getToneNoiseNumber()); ui->tnEditor->clearData(); for (auto& unit : instSSG->getToneNoiseSequence()) { ui->tnEditor->addSequenceData(unit.data); } for (auto& loop : instSSG->getToneNoiseLoopRoot().getAllLoops()) { ui->tnEditor->addLoop(loop.getBeginPos(), loop.getEndPos(), loop.getTimes()); } ui->tnEditor->setRelease(instSSG->getToneNoiseRelease()); if (instSSG->getToneNoiseEnabled()) { ui->tnEditGroupBox->setChecked(true); onToneNoiseNumberChanged(); } else { ui->tnEditGroupBox->setChecked(false); } } /********** Slots **********/ void SsgInstrumentEditor::onToneNoiseNumberChanged() { // Change users view std::multiset users = bt_.lock()->getToneNoiseSSGUsers(ui->tnNumSpinBox->value()); ui->tnUsersLineEdit->setText(inst_edit_utils::generateUsersString(users)); } void SsgInstrumentEditor::onToneNoiseParameterChanged(int tnNum) { if (ui->tnNumSpinBox->value() == tnNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentToneNoiseParameters(); } } void SsgInstrumentEditor::on_tnEditGroupBox_toggled(bool arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentSSGToneNoiseEnabled(instNum_, arg1); setInstrumentToneNoiseParameters(); emit toneNoiseNumberChanged(); emit modified(); } onToneNoiseNumberChanged(); } void SsgInstrumentEditor::on_tnNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentSSGToneNoise(instNum_, arg1); setInstrumentToneNoiseParameters(); emit toneNoiseNumberChanged(); emit modified(); } onToneNoiseNumberChanged(); } //--- Envelope void SsgInstrumentEditor::setInstrumentEnvelopeParameters() { Ui::EventGuard ev(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instSSG = dynamic_cast(inst.get()); ui->envNumSpinBox->setValue(instSSG->getEnvelopeNumber()); ui->envEditor->clearData(); for (auto& unit : instSSG->getEnvelopeSequence()) { QString str(""); if (unit.data >= 16) { if (unit.type == SSGEnvelopeUnit::RatioSubdata) { int r1, r2; unit.getSubdataAsRatio(r1, r2); str = QString("%1/%2").arg(r1).arg(r2); } else { str = QString::number(unit.subdata); } } ui->envEditor->addSequenceData(unit.data, str, unit.subdata); } for (auto& loop : instSSG->getEnvelopeLoopRoot().getAllLoops()) { ui->envEditor->addLoop(loop.getBeginPos(), loop.getEndPos(), loop.getTimes()); } ui->envEditor->setRelease(instSSG->getEnvelopeRelease()); if (instSSG->getEnvelopeEnabled()) { ui->envEditGroupBox->setChecked(true); onEnvelopeNumberChanged(); } else { ui->envEditGroupBox->setChecked(false); } } SSGEnvelopeUnit SsgInstrumentEditor::setEnvelopeSequenceColumn(int col, int envRow) { if (ui->hardFreqButtonGroup->checkedButton() == ui->hardFreqRawRadioButton) { SSGEnvelopeUnit unit = SSGEnvelopeUnit::makeRawUnit(envRow, ui->hardFreqRawSpinBox->value()); ui->envEditor->setText(col, QString::number(unit.subdata)); ui->envEditor->setSubdata(col, unit.subdata); return unit; } else { SSGEnvelopeUnit unit = SSGEnvelopeUnit::makeRatioUnit(envRow, ui->hardFreqToneSpinBox->value(), ui->hardFreqHardSpinBox->value()); ui->envEditor->setText(col, QString::number(ui->hardFreqToneSpinBox->value()) + "/" + QString::number(ui->hardFreqHardSpinBox->value())); ui->envEditor->setSubdata(col, unit.subdata); return unit; } } /********** Slots **********/ void SsgInstrumentEditor::onEnvelopeNumberChanged() { // Change users view std::multiset users = bt_.lock()->getEnvelopeSSGUsers(ui->envNumSpinBox->value()); ui->envUsersLineEdit->setText(inst_edit_utils::generateUsersString(users)); } void SsgInstrumentEditor::onEnvelopeParameterChanged(int envNum) { if (ui->envNumSpinBox->value() == envNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentEnvelopeParameters(); } } void SsgInstrumentEditor::on_envEditGroupBox_toggled(bool arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentSSGEnvelopeEnabled(instNum_, arg1); setInstrumentEnvelopeParameters(); emit envelopeNumberChanged(); emit modified(); } onEnvelopeNumberChanged(); } void SsgInstrumentEditor::on_envNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentSSGEnvelope(instNum_, arg1); setInstrumentEnvelopeParameters(); emit envelopeNumberChanged(); emit modified(); } onEnvelopeNumberChanged(); } void SsgInstrumentEditor::on_hardFreqRawSpinBox_valueChanged(int arg1) { ui->hardFreqRawSpinBox->setSuffix( QString(" (0x") + QString("%1 | ").arg(arg1, 4, 16, QChar('0')).toUpper() + QString("%1Hz").arg(arg1 ? QString::number(7800.0 / arg1, 'f', 4) : "-")); } //--- Arpeggio void SsgInstrumentEditor::setInstrumentArpeggioParameters() { Ui::EventGuard ev(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instSSG = dynamic_cast(inst.get()); ui->arpNumSpinBox->setValue(instSSG->getArpeggioNumber()); ui->arpEditor->clearData(); for (auto& unit : instSSG->getArpeggioSequence()) { ui->arpEditor->addSequenceData(unit.data); } for (auto& loop : instSSG->getArpeggioLoopRoot().getAllLoops()) { ui->arpEditor->addLoop(loop.getBeginPos(), loop.getEndPos(), loop.getTimes()); } ui->arpEditor->setRelease(instSSG->getArpeggioRelease()); for (int i = 0; i < ui->arpTypeComboBox->count(); ++i) { if (instSSG->getArpeggioType() == static_cast(ui->arpTypeComboBox->itemData(i).toInt())) { ui->arpTypeComboBox->setCurrentIndex(i); break; } } if (instSSG->getArpeggioEnabled()) { ui->arpEditGroupBox->setChecked(true); onArpeggioNumberChanged(); } else { ui->arpEditGroupBox->setChecked(false); } } /********** Slots **********/ void SsgInstrumentEditor::onArpeggioNumberChanged() { // Change users view std::multiset users = bt_.lock()->getArpeggioSSGUsers(ui->arpNumSpinBox->value()); ui->arpUsersLineEdit->setText(inst_edit_utils::generateUsersString(users)); } void SsgInstrumentEditor::onArpeggioParameterChanged(int tnNum) { if (ui->arpNumSpinBox->value() == tnNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentArpeggioParameters(); } } void SsgInstrumentEditor::onArpeggioTypeChanged(int) { auto type = static_cast(ui->arpTypeComboBox->currentData(Qt::UserRole).toInt()); if (!isIgnoreEvent_) { bt_.lock()->setArpeggioSSGType(ui->arpNumSpinBox->value(), type); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } ui->arpEditor->setSequenceType(type); } void SsgInstrumentEditor::on_arpEditGroupBox_toggled(bool arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentSSGArpeggioEnabled(instNum_, arg1); setInstrumentArpeggioParameters(); emit arpeggioNumberChanged(); emit modified(); } onArpeggioNumberChanged(); } void SsgInstrumentEditor::on_arpNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentSSGArpeggio(instNum_, arg1); setInstrumentArpeggioParameters(); emit arpeggioNumberChanged(); emit modified(); } onArpeggioNumberChanged(); } //--- Pitch void SsgInstrumentEditor::setInstrumentPitchParameters() { Ui::EventGuard ev(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instSSG = dynamic_cast(inst.get()); ui->ptNumSpinBox->setValue(instSSG->getPitchNumber()); ui->ptEditor->clearData(); for (auto& unit : instSSG->getPitchSequence()) { ui->ptEditor->addSequenceData(unit.data); } for (auto& loop : instSSG->getPitchLoopRoot().getAllLoops()) { ui->ptEditor->addLoop(loop.getBeginPos(), loop.getEndPos(), loop.getTimes()); } ui->ptEditor->setRelease(instSSG->getPitchRelease()); for (int i = 0; i < ui->ptTypeComboBox->count(); ++i) { if (instSSG->getPitchType() == static_cast(ui->ptTypeComboBox->itemData(i).toInt())) { ui->ptTypeComboBox->setCurrentIndex(i); break; } } if (instSSG->getPitchEnabled()) { ui->ptEditGroupBox->setChecked(true); onPitchNumberChanged(); } else { ui->ptEditGroupBox->setChecked(false); } } /********** Slots **********/ void SsgInstrumentEditor::onPitchNumberChanged() { // Change users view std::multiset users = bt_.lock()->getPitchSSGUsers(ui->ptNumSpinBox->value()); ui->ptUsersLineEdit->setText(inst_edit_utils::generateUsersString(users)); } void SsgInstrumentEditor::onPitchParameterChanged(int ptNum) { if (ui->ptNumSpinBox->value() == ptNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentPitchParameters(); } } void SsgInstrumentEditor::onPitchTypeChanged(int) { auto type = static_cast(ui->ptTypeComboBox->currentData(Qt::UserRole).toInt()); if (!isIgnoreEvent_) { bt_.lock()->setPitchSSGType(ui->ptNumSpinBox->value(), type); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } ui->ptEditor->setSequenceType(type); } void SsgInstrumentEditor::on_ptEditGroupBox_toggled(bool arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentSSGPitchEnabled(instNum_, arg1); setInstrumentPitchParameters(); emit pitchNumberChanged(); emit modified(); } onPitchNumberChanged(); } void SsgInstrumentEditor::on_ptNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentSSGPitch(instNum_, arg1); setInstrumentPitchParameters(); emit pitchNumberChanged(); emit modified(); } onPitchNumberChanged(); } BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/ssg_instrument_editor.hpp000066400000000000000000000107741476276175200305070ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include "instrument.hpp" #include "instrument/instrument_property_defs.hpp" #include "gui/instrument_editor/instrument_editor.hpp" #include "gui/instrument_editor/visualized_instrument_macro_editor.hpp" namespace Ui { class SsgInstrumentEditor; } class SsgInstrumentEditor final : public InstrumentEditor { Q_OBJECT public: SsgInstrumentEditor(int num, QWidget* parent = nullptr); ~SsgInstrumentEditor() override; /** * @brief Return sound source of instrument related to this dialog. * @return SSG. */ SoundSource getSoundSource() const override; /** * @brief Return instrument type of instrument related to this dialog. * @return SSG. */ InstrumentType getInstrumentType() const override; void updateByConfigurationChange() override; protected: void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; private: Ui::SsgInstrumentEditor *ui; bool isIgnoreEvent_; // SSG specific process called in settiing core / cofiguration / color palette. void updateBySettingCore() override; void updateBySettingConfiguration() override {} void updateBySettingColorPalette() override; void updateInstrumentParameters(); //========== Waveform ==========// signals: void waveformNumberChanged(); void waveformParameterChanged(int wfNum, int fromInstNum); public slots: void onWaveformNumberChanged(); void onWaveformParameterChanged(int wfNum); private: void setInstrumentWaveformParameters(); SSGWaveformUnit setWaveformSequenceColumn(int col, int wfRow); private slots: void on_waveEditGroupBox_toggled(bool arg1); void on_waveNumSpinBox_valueChanged(int arg1); void on_squareMaskRawSpinBox_valueChanged(int arg1); //========== Tone/Noise ==========// signals: void toneNoiseNumberChanged(); void toneNoiseParameterChanged(int tnNum, int fromInstNum); public slots: void onToneNoiseNumberChanged(); void onToneNoiseParameterChanged(int tnNum); private: void setInstrumentToneNoiseParameters(); private slots: void on_tnEditGroupBox_toggled(bool arg1); void on_tnNumSpinBox_valueChanged(int arg1); //========== Envelope ==========// signals: void envelopeNumberChanged(); void envelopeParameterChanged(int wfNum, int fromInstNum); public slots: void onEnvelopeNumberChanged(); void onEnvelopeParameterChanged(int envNum); private: void setInstrumentEnvelopeParameters(); SSGEnvelopeUnit setEnvelopeSequenceColumn(int col, int envRow); private slots: void on_envEditGroupBox_toggled(bool arg1); void on_envNumSpinBox_valueChanged(int arg1); void on_hardFreqRawSpinBox_valueChanged(int arg1); //========== Arpeggio ==========// signals: void arpeggioNumberChanged(); void arpeggioParameterChanged(int arpNum, int fromInstNum); public slots: void onArpeggioNumberChanged(); void onArpeggioParameterChanged(int arpNum); private: void setInstrumentArpeggioParameters(); private slots: void onArpeggioTypeChanged(int); void on_arpEditGroupBox_toggled(bool arg1); void on_arpNumSpinBox_valueChanged(int arg1); //========== Pitch ==========// signals: void pitchNumberChanged(); void pitchParameterChanged(int ptNum, int fromInstNum); public slots: void onPitchNumberChanged(); void onPitchParameterChanged(int ptNum); private: void setInstrumentPitchParameters(); private slots: void onPitchTypeChanged(int); void on_ptEditGroupBox_toggled(bool arg1); void on_ptNumSpinBox_valueChanged(int arg1); }; BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/ssg_instrument_editor.ui000066400000000000000000000500311476276175200303230ustar00rootroot00000000000000 SsgInstrumentEditor 0 0 507 390 Form 0 Waveform Waveform true false false # 127 Qt::NoFocus true Users: Qt::Horizontal 40 20 0 0 Square mask Raw true squareMaskButtonGroup (0x000 | -Hz) 4095 0 Tone/Mask ratio squareMaskButtonGroup 1 8 0 0 / 1 8 Tone/Noise Tone/Noise true false Qt::Horizontal 40 20 Users: false # 127 Qt::NoFocus true Envelope Envelope true false 0 0 Hardware envelope frequency Raw true hardFreqButtonGroup (0x0000 | -Hz) 0 65535 0 Tone/Env ratio hardFreqButtonGroup 1 8 0 0 / 1 8 Qt::Horizontal 40 20 false # 127 Users: Qt::NoFocus true Arpeggio Arpeggio true false Type: false # 127 Qt::Horizontal 40 20 Users: Qt::NoFocus true Pitch Pitch true false Users: Type: Qt::Horizontal 40 20 # 127 Qt::NoFocus true VisualizedInstrumentMacroEditor QWidget
gui/instrument_editor/visualized_instrument_macro_editor.hpp
1
ArpeggioMacroEditor QWidget
gui/instrument_editor/arpeggio_macro_editor.hpp
1
ToneNoiseMacroEditor QWidget
gui/instrument_editor/tone_noise_macro_editor.hpp
1
tabWidget waveEditGroupBox waveNumSpinBox squareMaskRawRadioButton squareMaskRawSpinBox squareMaskRatioRadioButton squareMaskToneSpinBox squareMaskMaskSpinBox tnEditGroupBox tnNumSpinBox envEditGroupBox envNumSpinBox hardFreqRawRadioButton hardFreqRawSpinBox hardFreqRatioRadioButton hardFreqToneSpinBox hardFreqHardSpinBox arpEditGroupBox arpNumSpinBox arpTypeComboBox ptEditGroupBox ptNumSpinBox ptTypeComboBox
BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/tone_noise_macro_editor.cpp000066400000000000000000000157721476276175200307440ustar00rootroot00000000000000/* * Copyright (C) 2019-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "tone_noise_macro_editor.hpp" #include #include ToneNoiseMacroEditor::ToneNoiseMacroEditor(QWidget *parent) : VisualizedInstrumentMacroEditor(parent) { setMaximumDisplayedRowCount(16); setDefaultRow(0); AddRow(tr("Tone"), false); AddRow(tr("Noise"), false); for (int i = 0; i < 32; ++i) { AddRow(QString::number(i), false); } autoFitLabelWidth(); } ToneNoiseMacroEditor::~ToneNoiseMacroEditor() = default; void ToneNoiseMacroEditor::drawField() { QPainter painter(&pixmap_); painter.setFont(font_); int dispCnt = getDisplayedRowCount(); int textOffset = fontAscend_ - fontHeight_ + fontLeading_ / 2; // Backgtound // Tone int thi = dispCnt - 1; int ty = std::accumulate(rowHeights_.begin(), rowHeights_.begin() + thi, 0); painter.fillRect(0, ty, panelWidth(), rowHeights_[static_cast(thi)], palette_->tnToneBackColor); // Noise int nhi = dispCnt - 2; int ny = std::accumulate(rowHeights_.begin(), rowHeights_.begin() + nhi, 0); painter.fillRect(0, ny, panelWidth(), rowHeights_[static_cast(nhi)], palette_->tnNoiseBackColor); // Row label painter.setPen(palette_->instSeqTagColor); if (isLabelOmitted_ && !labels_.empty()) { painter.drawText(1, rowHeights_.front() + textOffset, labels_[static_cast(upperRow_)]); int c = dispCnt / 2; painter.drawText(1, std::accumulate(rowHeights_.begin(), rowHeights_.begin() + c + 1, 0) + textOffset, labels_[static_cast(upperRow_ - c)]); int l = dispCnt - 3; painter.drawText(1, std::accumulate(rowHeights_.begin(), rowHeights_.begin() + l + 1, 0) + textOffset, labels_[static_cast(upperRow_ - l)]); } else { int l = dispCnt - 2; for (int i = 0; i < l; ++i) { painter.drawText(1, std::accumulate(rowHeights_.begin(), rowHeights_.begin() + i + 1, 0) + textOffset, labels_[static_cast(upperRow_ - i)]); } } // Noise painter.setPen(palette_->tnNoiseTextColor); int n = dispCnt - 2; painter.drawText(1, std::accumulate(rowHeights_.begin(), rowHeights_.begin() + n + 1, 0) + textOffset, labels_[static_cast(1)]); // Tone painter.setPen(palette_->tnToneTextColor); int t = dispCnt - 1; painter.drawText(1, std::accumulate(rowHeights_.begin(), rowHeights_.begin() + t + 1, 0) + textOffset, labels_[static_cast(0)]); for (size_t i = 1; i < cols_.size(); i += 2) { painter.fillRect(labWidth_ + std::accumulate(colWidths_.begin(), colWidths_.begin() + static_cast(i), 0), 0, colWidths_[i], std::accumulate(rowHeights_.begin(), rowHeights_.begin() + dispCnt - 2, 0), palette_->instSeqOddColColor); } // Sequence for (size_t i = 0; i < cols_.size(); ++i) { int v = cols_[i].row; int x = labWidth_ + std::accumulate(colWidths_.begin(), colWidths_.begin() + static_cast(i), 0); if (!v || (32 < v && v < 65)) { // Tone painter.fillRect(x, ty, colWidths_[i], rowHeights_[static_cast(thi)], palette_->tnToneCellColor); } if (0 < v && v < 65) { // Noise painter.fillRect(x, ny, colWidths_[i], rowHeights_[static_cast(nhi)], palette_->tnNoiseCellColor); // Noise period int r = (v - 1) % 32 + 2; if (upperRow_ >= r && r > upperRow_ - dispCnt + 2) { int hi = upperRow_ - r; int y = std::accumulate(rowHeights_.begin(), rowHeights_.begin() + hi, 0); painter.fillRect(x, y, colWidths_[i], rowHeights_[static_cast(hi)], palette_->instSeqCellColor); } } } if (hovCol_ >= 0 && hovRow_ >= 0) { painter.fillRect(labWidth_ + std::accumulate(colWidths_.begin(), colWidths_.begin() + hovCol_, 0), std::accumulate(rowHeights_.begin(), rowHeights_.begin() + hovRow_, 0), colWidths_[static_cast(hovCol_)], rowHeights_[static_cast(hovRow_)], palette_->instSeqHovColor); } } int ToneNoiseMacroEditor::detectRowNumberForMouseEvent(int col, int internalRow) const { int r = upperRow_ - internalRow; int b = upperRow_ - getDisplayedRowCount() + 1; int v = cols_.at(static_cast(col)).row; if (r == b) { // Tone if (!v) return 65; // Tone to None else if (v == 65) return 0; // None to Tone else if (v < 33) return v + 32; // Noise to Tone+Noise else return v - 32; // Tone+Noise to Noise } else if (r == b + 1) { // Noise if (!v) return 33; // Tone to Tone+Noise(0) else if (v == 65) return 1; // None to Noise(0) else if (v < 33) return 65; // Noise to None else return 0; // Tone+Noise to Tone } else { // Noise period r -= 2; // Set noise period if (0 < v && v < 33) return 1 + r; // Noise else return 33 + r; // Tone+Noise } } int ToneNoiseMacroEditor::maxInMML() const { return 66; } QString ToneNoiseMacroEditor::convertSequenceDataUnitToMML(Column col) { if (col.row == 0) return "t"; else { int p = (col.row - 1) % 32; if (col.row == 65) return "u"; else if (col.row < 33) return QString("%1n").arg(p); else return QString("%1tn").arg(p); } } bool ToneNoiseMacroEditor::interpretDataInMML(QString &text, int &cnt, std::vector &column) { // Tone+Noise QRegularExpressionMatch m = QRegularExpression("^(\\d+)(nt|tn)").match(text); if (m.hasMatch()) { int p = m.captured(1).toInt(); if (p < 0 || 31 < p) return false; column.push_back({ 33 + p, -1, "" }); ++cnt; text.remove(QRegularExpression("^\\d+(nt|tn)")); return true; } // Noise m = QRegularExpression("^(\\d+)n").match(text); if (m.hasMatch()) { int p = m.captured(1).toInt(); if (p < 0 || 31 < p) return false; column.push_back({ 1 + p, -1, "" }); ++cnt; text.remove(QRegularExpression("^\\d+n")); return true; } // Tone m = QRegularExpression(R"(^(\d+)?t)").match(text); if (m.hasMatch()) { column.push_back({ 0, -1, "" }); ++cnt; text.remove(QRegularExpression("^(\\d+)?t")); return true; } // None if (text.startsWith("u")) { column.push_back({ 65, -1, "" }); ++cnt; text.remove(0, 1); return true; } return false; } BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/tone_noise_macro_editor.hpp000066400000000000000000000035561476276175200307460ustar00rootroot00000000000000/* * Copyright (C) 2019 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef TONENOISEMACROEDITOR_HPP #define TONENOISEMACROEDITOR_HPP #include "visualized_instrument_macro_editor.hpp" class ToneNoiseMacroEditor final : public VisualizedInstrumentMacroEditor { Q_OBJECT // Only change drawing process. // The values of ``cols_`` is left as raw values. // For example, the value of "Tone+Noise 2" is 35. public: explicit ToneNoiseMacroEditor(QWidget *parent = nullptr); ~ToneNoiseMacroEditor() override; protected: void drawField() override; int detectRowNumberForMouseEvent(int col, int internalRow) const override; int maxInMML() const override; QString convertSequenceDataUnitToMML(Column col) override; bool interpretDataInMML(QString &text, int &cnt, std::vector &column) override; }; #endif // TONENOISEMACROEDITOR_HPP BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/visualized_instrument_macro_editor.cpp000066400000000000000000000765651476276175200332600ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "visualized_instrument_macro_editor.hpp" #include "ui_visualized_instrument_macro_editor.h" #include #include #include #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include #else #include #endif #include #include #include "gui/event_guard.hpp" #include "enum_hash.hpp" VisualizedInstrumentMacroEditor::VisualizedInstrumentMacroEditor(QWidget *parent) : QWidget(parent), font_(QApplication::font()), met_(font_), maxDispRowCnt_(0), upperRow_(-1), defaultRow_(0), hovRow_(-1), hovCol_(-1), type_(SequenceType::PlainSequence), permittedReleaseType_(PermittedReleaseFlag::FIXED_RELEASE), isLabelOmitted_(false), release_(InstrumentSequenceRelease::NoRelease), ui(new Ui::VisualizedInstrumentMacroEditor), pressRow_(-1), pressCol_(-1), grabLoop_(-1), isGrabLoopHead_(false), isGrabRelease_(false), isMultiReleaseState_(false), mmlBase_(0), isIgnoreEvent_(false) { ui->setupUi(this); /* Font */ font_.setPointSize(10); // Check font size #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) fontWidth_ = met_.horizontalAdvance('0'); #else fontWidth_ = met_.width('0'); #endif fontAscend_ = met_.ascent(); fontHeight_ = met_.height(); fontLeading_ = met_.leading(); /* Width & height */ autoFitLabelWidth(); ui->panel->setAttribute(Qt::WA_Hover); ui->verticalScrollBar->setVisible(false); ui->panel->installEventFilter(this); } VisualizedInstrumentMacroEditor::~VisualizedInstrumentMacroEditor() { delete ui; } void VisualizedInstrumentMacroEditor::setColorPalette(std::shared_ptr palette) { palette_ = palette; } void VisualizedInstrumentMacroEditor::AddRow(QString label, bool fitLabelWidth) { labels_.push_back(label); if (static_cast(labels_.size()) <= maxDispRowCnt_) { upperRow_ = static_cast(labels_.size()) - 1; ui->verticalScrollBar->setVisible(false); ui->verticalScrollBar->setMaximum(0); } else { ui->verticalScrollBar->setVisible(true); int max = static_cast(labels_.size()) - maxDispRowCnt_; ui->verticalScrollBar->setMaximum(max); ui->verticalScrollBar->setValue(max); } if (fitLabelWidth) autoFitLabelWidth(); updateRowHeight(); } void VisualizedInstrumentMacroEditor::setMaximumDisplayedRowCount(int count) { maxDispRowCnt_ = count; if (static_cast(labels_.size()) <= maxDispRowCnt_) { upperRow_ = static_cast(labels_.size()) - 1; ui->verticalScrollBar->setVisible(false); ui->verticalScrollBar->setMaximum(0); } else { ui->verticalScrollBar->setVisible(true); int max = static_cast(labels_.size()) - maxDispRowCnt_; ui->verticalScrollBar->setMaximum(max); ui->verticalScrollBar->setValue(max); } updateRowHeight(); } void VisualizedInstrumentMacroEditor::setDefaultRow(int row) { defaultRow_ = row; } int VisualizedInstrumentMacroEditor::getSequenceLength() const { return static_cast(cols_.size()); } void VisualizedInstrumentMacroEditor::setSequenceData(int row, int col, QString str, int subdata) { size_t idx = static_cast(col); cols_.at(idx).row = row; cols_.at(idx).text = str; cols_.at(idx).data = subdata; ui->panel->update(); printMML(); emit sequenceDataChanged(row, col); } void VisualizedInstrumentMacroEditor::setText(int col, QString text) { cols_.at(static_cast(col)).text = text; } void VisualizedInstrumentMacroEditor::setSubdata(int col, int subdata) { cols_.at(static_cast(col)).data = subdata; printMML(); } int VisualizedInstrumentMacroEditor::getSequenceAt(int col) const { return cols_.at(static_cast(col)).row; } int VisualizedInstrumentMacroEditor::getSequenceDataAt(int col) const { return cols_.at(static_cast(col)).data; } void VisualizedInstrumentMacroEditor::setMultipleReleaseState(bool enabled) { isMultiReleaseState_ = enabled; } void VisualizedInstrumentMacroEditor::addSequenceData(int row, QString str, int subdata) { cols_.push_back({ row, subdata, str }); updateColumnWidth(); ui->panel->update(); ui->colSizeLabel->setText(tr("Size: %1").arg(cols_.size())); printMML(); emit sequenceDataAdded(row, static_cast(cols_.size()) - 1); } void VisualizedInstrumentMacroEditor::removeSequenceData() { if (cols_.size() == 1) return; cols_.pop_back(); // Modify loop for (size_t i = 0; i < loops_.size();) { if (loops_[i].begin >= static_cast(cols_.size())) { loops_.erase(loops_.begin() + static_cast(i)); } else { if (loops_[i].end >= static_cast(cols_.size())) loops_[i].end = static_cast(cols_.size()) - 1; ++i; } } // Modify release if (release_.getBeginPos() >= static_cast(cols_.size())) release_.disable(); updateColumnWidth(); ui->panel->update(); ui->colSizeLabel->setText(tr("Size: %1").arg(cols_.size())); printMML(); emit sequenceDataRemoved(); } void VisualizedInstrumentMacroEditor::addLoop(int begin, int end, int times) { int inx = 0; for (size_t i = 0; i < loops_.size(); ++i) { if (loops_[i].begin > begin) { break; } ++inx; } loops_.insert(loops_.begin() + inx, { begin, end, times }); printMML(); emit loopAdded(InstrumentSequenceLoop(begin, end, times)); } void VisualizedInstrumentMacroEditor::setSequenceType(SequenceType type) { type_ = type; updateLabels(); printMML(); } void VisualizedInstrumentMacroEditor::setPermittedReleaseTypes(int types) { permittedReleaseType_ = types; } void VisualizedInstrumentMacroEditor::setRelease(const InstrumentSequenceRelease& release) { release_ = release; printMML(); } void VisualizedInstrumentMacroEditor::clearData() { cols_.clear(); loops_.clear(); release_.disable(); updateColumnWidth(); printMML(); } void VisualizedInstrumentMacroEditor::clearRow() { labels_.clear(); autoFitLabelWidth(); } void VisualizedInstrumentMacroEditor::setUpperRow(int row) { upperRow_ = row; int pos = upperRow_ + 1 - getDisplayedRowCount(); ui->panel->update(); ui->verticalScrollBar->setValue(ui->verticalScrollBar->maximum() - pos); } void VisualizedInstrumentMacroEditor::setLabel(int row, QString text) { labels_.at(static_cast(row)) = text; autoFitLabelWidth(); ui->panel->update(); } void VisualizedInstrumentMacroEditor::clearAllLabelText() { std::fill(labels_.begin(), labels_.end(), ""); autoFitLabelWidth(); ui->panel->update(); } void VisualizedInstrumentMacroEditor::setLabelDiaplayMode(bool isOmitted) { isLabelOmitted_ = isOmitted; ui->panel->update(); } void VisualizedInstrumentMacroEditor::autoFitLabelWidth() { #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) int w = met_.horizontalAdvance(VisualizedInstrumentMacroEditor::tr("Release") + " "); for (auto& lab : labels_) w = std::max(w, met_.horizontalAdvance(lab + " ")); #else int w = met_.width(VisualizedInstrumentMacroEditor::tr("Release") + " "); for (auto& lab : labels_) w = std::max(w, met_.width(lab + " ")); #endif labWidth_ = w; } /******************************/ void VisualizedInstrumentMacroEditor::initDisplay() { pixmap_ = QPixmap(ui->panel->geometry().size()); } void VisualizedInstrumentMacroEditor::drawField() { QPainter painter(&pixmap_); painter.setFont(font_); int textOffset = fontAscend_ - fontHeight_ + fontLeading_ / 2; // Row label painter.setPen(palette_->instSeqTagColor); int dispCnt = getDisplayedRowCount(); if (isLabelOmitted_ && !labels_.empty()) { painter.drawText(1, rowHeights_.front() + textOffset, labels_[static_cast(upperRow_)]); int c = dispCnt / 2; painter.drawText(1, std::accumulate(rowHeights_.begin(), rowHeights_.begin() + c + 1, 0) + textOffset, labels_[static_cast(upperRow_ - c)]); int l = dispCnt - 1; painter.drawText(1, std::accumulate(rowHeights_.begin(), rowHeights_.begin() + l + 1, 0) + textOffset, labels_[static_cast(upperRow_ - l)]); } else { for (int i = 0; i < dispCnt; ++i) { painter.drawText(1, std::accumulate(rowHeights_.begin(), rowHeights_.begin() + i + 1, 0) + textOffset, labels_[static_cast(upperRow_ - i)]); } } for (size_t i = 1; i < cols_.size(); i += 2) { painter.fillRect(labWidth_ + std::accumulate(colWidths_.begin(), colWidths_.begin() + static_cast(i), 0), 0, colWidths_[i], fieldHeight_, palette_->instSeqOddColColor); } // Sequence painter.setPen(palette_->instSeqCellTextColor); for (size_t i = 0; i < cols_.size(); ++i) { if (upperRow_ >= cols_[i].row && cols_[i].row > upperRow_ - dispCnt) { int x = labWidth_ + std::accumulate(colWidths_.begin(), colWidths_.begin() + static_cast(i), 0); int hi = upperRow_ - cols_[i].row; int y = std::accumulate(rowHeights_.begin(), rowHeights_.begin() + hi, 0); painter.fillRect(x, y, colWidths_[i], rowHeights_[static_cast(hi)], palette_->instSeqCellColor); painter.drawText(x + 2, y + rowHeights_[static_cast(upperRow_ - cols_[i].row)] + textOffset, cols_[i].text); } } if (hovCol_ >= 0 && hovRow_ >= 0) { painter.fillRect(labWidth_ + std::accumulate(colWidths_.begin(), colWidths_.begin() + hovCol_, 0), std::accumulate(rowHeights_.begin(), rowHeights_.begin() + hovRow_, 0), colWidths_[static_cast(hovCol_)], rowHeights_[static_cast(hovRow_)], palette_->instSeqHovColor); } } void VisualizedInstrumentMacroEditor::drawLoop() { QPainter painter(&pixmap_); painter.setFont(font_); painter.fillRect(0, loopY_, panelWidth(), fontHeight_, palette_->instSeqLoopBackColor); painter.setPen(palette_->instSeqLoopTextColor); painter.drawText(1, loopBaseY_, tr("Loop")); int w = labWidth_; int seqLen = static_cast(cols_.size()); for (int i = 0; i < seqLen; ++i) { for (size_t j = 0; j < loops_.size(); ++j) { if (loops_[j].begin <= i && i <= loops_[j].end) { int colWidth = colWidths_[static_cast(i)]; painter.fillRect(w, loopY_, colWidth, fontHeight_, palette_->instSeqLoopColor); if (loops_[j].begin == i) { painter.fillRect(w, loopY_, 2, fontHeight_, palette_->instSeqLoopEdgeColor); QString times = (loops_[j].times == 1) ? "" : QString::number(loops_[j].times); painter.drawText(QRectF(w + 2, loopY_, colWidth - 2, fontHeight_), Qt::AlignLeft | Qt::AlignVCenter, tr("Loop %1").arg(times)); } if (loops_[j].end == i) { painter.fillRect(w + colWidth - 2, loopY_, 2, fontHeight_, palette_->instSeqLoopEdgeColor); } } } if (hovRow_ == -2 && hovCol_ == i) painter.fillRect(w, loopY_, colWidths_[static_cast(i)], fontHeight_, palette_->instSeqHovColor); w += colWidths_[static_cast(i)]; } } void VisualizedInstrumentMacroEditor::drawRelease() { QPainter painter(&pixmap_); painter.setFont(font_); painter.fillRect(0, releaseY_, panelWidth(), fontHeight_, palette_->instSeqReleaseBackColor); painter.setPen(palette_->instSeqReleaseTextColor); painter.drawText(1, releaseBaseY_, tr("Release")); static const std::unordered_map DISP_TEXT_MAP = { { InstrumentSequenceRelease::NoRelease, "" }, { InstrumentSequenceRelease::FixedRelease, tr("Fixed") }, { InstrumentSequenceRelease::AbsoluteRelease, tr("Absolute") }, { InstrumentSequenceRelease::RelativeRelease, tr("Relative") } }; int w = labWidth_; int seqLen = static_cast(cols_.size()); for (int i = 0; i < seqLen; ++i) { if (release_.getBeginPos() == i) { painter.fillRect(w, releaseY_, panelWidth() - w, fontHeight_, palette_->instSeqReleaseColor); painter.fillRect(w, releaseY_, 2, fontHeight_, palette_->instSeqReleaseEdgeColor); painter.setPen(palette_->instSeqReleaseTextColor); painter.drawText(w + 2, releaseBaseY_, DISP_TEXT_MAP.at(release_.getType())); } if (hovRow_ == -3 && hovCol_ == i) painter.fillRect(w, releaseY_, colWidths_[static_cast(i)], fontHeight_, palette_->instSeqHovColor); w += colWidths_[static_cast(i)]; } } void VisualizedInstrumentMacroEditor::drawBorder() { QPainter painter(&pixmap_); painter.setPen(palette_->instSeqBorderColor); painter.drawLine(labWidth_, 0, labWidth_, ui->panel->geometry().height()); for (int i = 1; i < maxDispRowCnt_; ++i) { painter.drawLine(labWidth_, std::accumulate(rowHeights_.begin(), rowHeights_.begin() + i, 0), panelWidth(), std::accumulate(rowHeights_.begin(), rowHeights_.begin() + i, 0)); } } void VisualizedInstrumentMacroEditor::drawShadow() { QPainter painter(&pixmap_); painter.fillRect(0, 0, panelWidth(), ui->panel->geometry().height(), palette_->instSeqMaskColor); } void VisualizedInstrumentMacroEditor::updateLabels() { } void VisualizedInstrumentMacroEditor::printMML() { if (cols_.empty()) return; QString text = ""; std::vector lstack; static const std::unordered_map DISP_REL_MAP = { { InstrumentSequenceRelease::NoRelease, "" }, { InstrumentSequenceRelease::FixedRelease, "| " }, { InstrumentSequenceRelease::AbsoluteRelease, "/ " }, { InstrumentSequenceRelease::RelativeRelease, ": " } }; int seqLen = static_cast(cols_.size()); for (int cnt = 0; cnt < seqLen; ++cnt) { if (release_.getBeginPos() == cnt) { text += DISP_REL_MAP.at(release_.getType()); } for (size_t i = 0; i < loops_.size(); ++i) { if (loops_[i].begin == cnt) { lstack.push_back(loops_[i]); text += "[ "; } else if (loops_[i].begin > cnt) { break; } } text += (convertSequenceDataUnitToMML(cols_[static_cast(cnt)]) + " "); while (!lstack.empty()) { if (lstack.back().end == cnt) { text += "]"; if (lstack.back().times > 1) { text += QString::number(lstack.back().times); } text += " "; lstack.pop_back(); } else { break; } } } ui->lineEdit->setText(text); } QString VisualizedInstrumentMacroEditor::convertSequenceDataUnitToMML(Column col) { return QString::number(col.row + mmlBase_); } void VisualizedInstrumentMacroEditor::interpretMML() { if (cols_.empty()) return; QString text = ui->lineEdit->text(); std::vector column; std::vector loop; InstrumentSequenceLoopRoot loopRoot(1); std::deque lbstack; std::deque loopStack; InstrumentSequenceRelease release(InstrumentSequenceRelease::NoRelease); std::vector lstack; int cnt = 0; while (!text.isEmpty()) { QRegularExpressionMatch m = QRegularExpression("^\\[").match(text); if (m.hasMatch()) { loop.push_back({ cnt, cnt, 1 }); lstack.push_back(loop.size() - 1); lbstack.push_back(cnt); text.remove(QRegularExpression("^\\[")); continue; } m = QRegularExpression("^\\](\\d*)").match(text); if (m.hasMatch()) { if (lstack.empty() || cnt == 0) return; loop[lstack.back()].end = cnt - 1; if (!m.captured(1).isEmpty()) { int t = m.captured(1).toInt(); if (t > 1) loop[lstack.back()].times = t; else return; } lstack.pop_back(); if (lbstack.empty() || lbstack.back() == cnt) return; if (!m.captured(1).isEmpty()) { int t = m.captured(1).toInt(); if (t > 1) loopStack.push_back(InstrumentSequenceLoop(lbstack.back(), cnt - 1, t)); else return; } else { loopStack.push_back(InstrumentSequenceLoop(lbstack.back(), cnt - 1)); } lbstack.pop_back(); text.remove(QRegularExpression("^\\]\\d*")); continue; } if (permittedReleaseType_ & PermittedReleaseFlag::FIXED_RELEASE) { m = QRegularExpression("^\\|").match(text); if (m.hasMatch()) { if (release.isEnabled()) return; release.setType(InstrumentSequenceRelease::FixedRelease); release.setBeginPos(cnt); text.remove(QRegularExpression("^\\|")); continue; } } if (permittedReleaseType_ & PermittedReleaseFlag::ABSOLUTE_RELEASE) { m = QRegularExpression("^/").match(text); if (m.hasMatch()) { if (release.isEnabled()) return; release.setType(InstrumentSequenceRelease::AbsoluteRelease); release.setBeginPos(cnt); text.remove(QRegularExpression("^/")); continue; } } if (permittedReleaseType_ & PermittedReleaseFlag::RELATIVE_RELEASE) { m = QRegularExpression("^:").match(text); if (m.hasMatch()) { if (release.isEnabled()) return; release.setType(InstrumentSequenceRelease::RelativeRelease); release.setBeginPos(cnt); text.remove(QRegularExpression("^:")); continue; } } if (interpretSlopeInMML(text, cnt, column)) { // Slope loopRoot.resize(cnt); continue; } if (interpretDataInMML(text, cnt, column)) { // Data loopRoot.resize(cnt); continue; } m = QRegularExpression("^ +").match(text); if (m.hasMatch()) { text.remove(QRegularExpression("^ +")); continue; } return; } if (column.empty()) return; if (!lstack.empty()) return; if (!lbstack.empty()) return; if (release.isEnabled() && release.getBeginPos() >= static_cast(column.size())) return; while (cols_.size() > 1) removeSequenceData(); setSequenceData(column.front().row, 0); for (size_t i = 1; i < column.size(); ++i) { addSequenceData(column[i].row); } loops_ = loop; emit loopCleared(); // Reverse order because deepest is first in for (auto itr = loopStack.crbegin(); itr != loopStack.crend(); ++itr) { emit loopAdded(*itr); } release_ = release; emit releaseChanged(release); ui->panel->update(); } bool VisualizedInstrumentMacroEditor::interpretSlopeInMML(QString& text, int& cnt, std::vector& column) { QRegularExpressionMatch m = QRegularExpression("^(?-?\\d+) *_ *(?-?\\d+)( *_ *(?-?\\d+))?").match(text); if (m.hasMatch()) { int start = m.captured("start").toInt(); int end = m.captured("end").toInt(); QString diffStr = m.captured("diff"); if (start < 0 || maxInMML() <= start || end < 0 || maxInMML() <= end || start == end) return false; int diff; int d = start; if (start < end) { if (diffStr.isEmpty()) { diff = 1; } else { diff = diffStr.toInt(); if (diff <= 0) return false; } for (bool flag = true; flag ; ) { if (end <= d) { flag = false; d = end; } column.push_back({ d, -1, "" }); d += diff; ++cnt; } } else { if (diffStr.isEmpty()) { diff = -1; } else { diff = diffStr.toInt(); if (0 <= diff) return false; } for (bool flag = true; flag ; ) { if (d <= end) { flag = false; d = end; } column.push_back({ d, -1, "" }); d += diff; ++cnt; } } text.remove(QRegularExpression("^-?\\d+ *_ *-?\\d+( *_ *-?\\d+)?")); return true; } return false; } bool VisualizedInstrumentMacroEditor::interpretDataInMML(QString& text, int& cnt, std::vector& column) { QRegularExpressionMatch m = QRegularExpression("^(-?\\d+)").match(text); if (m.hasMatch()) { int d = m.captured(1).toInt() - mmlBase_; if (d < 0 || maxInMML() <= d) return false; column.push_back({ d, -1, "" }); ++cnt; text.remove(QRegularExpression("^-?\\d+")); return true; } return false; } int VisualizedInstrumentMacroEditor::checkLoopRegion(int col) { int ret = -1; for (size_t i = 0; i < loops_.size(); ++i) { if (loops_[i].begin <= col) { if (loops_[i].end >= col) { ret = static_cast(i); } } else { break; } } return ret; } void VisualizedInstrumentMacroEditor::moveLoop() { if (hovCol_ < 0) return; size_t glabIdxU = static_cast(grabLoop_); auto& tgtLoopRef = loops_[glabIdxU]; int prevBegin = tgtLoopRef.begin; int prevEnd = tgtLoopRef.end; if (isGrabLoopHead_) { if (hovCol_ < prevBegin) { if (glabIdxU && hovCol_ <= loops_[glabIdxU - 1].end) { tgtLoopRef.begin = loops_[glabIdxU - 1].end + 1; } else { tgtLoopRef.begin = hovCol_; } } else if (prevBegin < hovCol_) { if (prevEnd < hovCol_) { loops_.erase(loops_.begin() + grabLoop_); emit loopRemoved(prevBegin, prevEnd); printMML(); return; } else { tgtLoopRef.begin = hovCol_; } } else return; } else { if (hovCol_ < prevEnd) { if (hovCol_ < prevBegin) { loops_.erase(loops_.begin() + grabLoop_); emit loopRemoved(prevBegin, prevEnd); printMML(); return; } else { tgtLoopRef.end = hovCol_; } } else if (prevEnd < hovCol_) { if (glabIdxU < loops_.size() - 1 && loops_[glabIdxU + 1].begin <= hovCol_) { tgtLoopRef.end = loops_[glabIdxU + 1].begin - 1; } else { tgtLoopRef.end = hovCol_; } } else return; } emit loopChanged(prevBegin, prevEnd, InstrumentSequenceLoop(tgtLoopRef.begin, tgtLoopRef.end, tgtLoopRef.times)); printMML(); } void VisualizedInstrumentMacroEditor::setMMLDisplay0As(int n) { mmlBase_ = n; } int VisualizedInstrumentMacroEditor::panelWidth() const { return ui->panel->geometry().width(); } /********** Events **********/ bool VisualizedInstrumentMacroEditor::eventFilter(QObject*object, QEvent* event) { if (object == ui->panel) { switch (event->type()) { case QEvent::Paint: paintEventInView(dynamic_cast(event)); return false; case QEvent::Resize: resizeEventInView(dynamic_cast(event)); return false; case QEvent::MouseButtonPress: if (isEnabled()) mousePressEventInView(dynamic_cast(event)); return false; case QEvent::MouseButtonDblClick: if (isEnabled()) mousePressEventInView(dynamic_cast(event)); return false; case QEvent::MouseButtonRelease: if (isEnabled()) mouseReleaseEventInView(dynamic_cast(event)); return false; case QEvent::MouseMove: if (isEnabled()) mouseMoveEventInView(); return true; case QEvent::HoverMove: mouseHoverdEventInView(dynamic_cast(event)); return false; case QEvent::Leave: leaveEventInView(); return false; case QEvent::Wheel: wheelEventInView(dynamic_cast(event)); return false; default: return false; } } return QWidget::eventFilter(object, event); } void VisualizedInstrumentMacroEditor::paintEventInView(QPaintEvent* event) { if (!palette_) return; pixmap_.fill(Qt::black); drawField(); drawLoop(); drawRelease(); drawBorder(); if (!isEnabled()) drawShadow(); QPainter painter(ui->panel); painter.drawPixmap(event->rect(), pixmap_, event->rect()); } void VisualizedInstrumentMacroEditor::resizeEventInView(QResizeEvent* event) { Q_UNUSED(event) updateRowHeight(); updateColumnWidth(); releaseY_ = ui->panel->geometry().height() - fontHeight_; releaseBaseY_ = releaseY_ + fontAscend_ + fontLeading_ / 2; loopY_ = releaseY_ - fontHeight_; loopBaseY_ = releaseBaseY_ - fontHeight_; fieldHeight_ = loopY_; initDisplay(); } int VisualizedInstrumentMacroEditor::detectRowNumberForMouseEvent(int col, int internalRow) const { Q_UNUSED(col) return upperRow_ - internalRow; } void VisualizedInstrumentMacroEditor::mousePressEventInView(QMouseEvent* event) { if (!cols_.size()) return; pressRow_ = hovRow_; pressCol_ = hovCol_; // Check grab int x = event->pos().x(); if (hovRow_ == -2) { if (event->button() == Qt::LeftButton) { int seqLen = static_cast(cols_.size()); for (int col = 0, w = labWidth_; col < seqLen; ++col) { if (w - 4 < x && x < w + 4) { for (size_t i = 0; i < loops_.size(); ++i) { if (loops_[i].begin == col) { grabLoop_ = static_cast(i); isGrabLoopHead_ = true; } else if (loops_[i].begin > col) { break; } } } else if (w + colWidths_[static_cast(col)] - 4 < x && x < w + colWidths_[static_cast(col)] + 4) { for (size_t i = 0; i < loops_.size(); ++i) { if (loops_[i].end == col) { grabLoop_ = static_cast(i); isGrabLoopHead_ = false; } else if (loops_[i].end > col) { break; } } } w += colWidths_[static_cast(col)]; } } } else if (hovRow_ == -3 && release_.isEnabled()) { if (event->button() == Qt::LeftButton) { int w = labWidth_ + std::accumulate(colWidths_.begin(), colWidths_.begin() + release_.getBeginPos(), 0); if (w - 4 < x && x < w + 4) { isGrabRelease_ = true; } } } // Press process if (pressCol_ > -1) { if (pressRow_ == -2) { if (grabLoop_ == -1) { int i = checkLoopRegion(pressCol_); switch (event->button()) { case Qt::LeftButton: { if (i == -1) { // New loop addLoop(pressCol_, pressCol_, 1); } else { // Loop count up ++loops_[static_cast(i)].times; printMML(); auto& l = loops_[static_cast(i)]; emit loopChanged(l.begin, l.end, InstrumentSequenceLoop(l.begin, l.end, l.times)); } break; } case Qt::RightButton: { if (i > -1) { // Loop count down if (loops_[static_cast(i)].times > 1) { --loops_[static_cast(i)].times; auto& l = loops_[static_cast(i)]; emit loopChanged(l.begin, l.end, InstrumentSequenceLoop(l.begin, l.end, l.times)); } else { // Erase loop auto& l = loops_[static_cast(i)]; emit loopRemoved(l.begin, l.end); loops_.erase(loops_.begin() + i); } printMML(); } break; } default: break; } } } else if (pressRow_ == -3) { if (!isGrabRelease_) { switch (event->button()) { case Qt::LeftButton: { if (!release_.isEnabled() || pressCol_ < release_.getBeginPos()) { // New release if (!release_.isEnabled()) release_.setType(InstrumentSequenceRelease::FixedRelease); release_.setBeginPos(pressCol_); printMML(); emit releaseChanged(release_); } else if (isMultiReleaseState_) { // Change release type switch (release_.getType()) { case InstrumentSequenceRelease::FixedRelease: release_.setType(InstrumentSequenceRelease::AbsoluteRelease); break; case InstrumentSequenceRelease::AbsoluteRelease: release_.setType(InstrumentSequenceRelease::RelativeRelease); break; case InstrumentSequenceRelease::NoRelease: case InstrumentSequenceRelease::RelativeRelease: release_.setType(InstrumentSequenceRelease::FixedRelease); break; } printMML(); emit releaseChanged(release_); } break; } case Qt::RightButton: { if (pressCol_ >= release_.getBeginPos()) { // Erase release release_.disable(); printMML(); emit releaseChanged(release_); } break; } default: break; } } } else { setSequenceData(detectRowNumberForMouseEvent(hovCol_, hovRow_), hovCol_); prevPressRow_ = hovRow_; prevPressCol_ = hovCol_; } } ui->panel->update(); } void VisualizedInstrumentMacroEditor::mouseReleaseEventInView(QMouseEvent* event) { if (!cols_.size()) return; if (grabLoop_ != -1) { // Move loop if (event->button() == Qt::LeftButton) { moveLoop(); } } else if (isGrabRelease_) { // Move release if (event->button() == Qt::LeftButton) { if (hovCol_ > -1) { release_.setBeginPos(hovCol_); printMML(); emit releaseChanged(release_); } } } pressRow_ = -1; pressCol_ = -1; prevPressRow_ = -1; prevPressCol_ = -1; grabLoop_ = -1; isGrabLoopHead_ = false; isGrabRelease_ = false; ui->panel->update(); } void VisualizedInstrumentMacroEditor::mouseMoveEventInView() { if (!cols_.size()) return; if (pressRow_ >= 0 && pressCol_ >= 0 && hovRow_ >= 0 && hovCol_ >= 0) { if (prevPressRow_ != hovRow_ || prevPressCol_ != hovCol_) { prevPressRow_ = hovRow_; prevPressCol_ = hovCol_; setSequenceData(detectRowNumberForMouseEvent(hovCol_, hovRow_), hovCol_); } } } void VisualizedInstrumentMacroEditor::mouseHoverdEventInView(QHoverEvent* event) { if (!cols_.size()) return; int oldCol = hovCol_; int oldRow = hovRow_; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QPointF pos = event->position(); #else QPoint pos = event->pos(); #endif // Detect column if (pos.x() < labWidth_) { hovCol_ = -2; } else { int seqLen = static_cast(cols_.size()); for (int i = 0, w = labWidth_; i < seqLen; ++i) { w += colWidths_[static_cast(i)]; if (pos.x() < w) { hovCol_ = i; break; } } if (hovCol_ >= seqLen) hovCol_ = -1; // Out of range } // Detect row if (releaseY_ < pos.y()) { hovRow_ = -3; } else if (loopY_ < pos.y()) { hovRow_ = -2; } else { int cnt = getDisplayedRowCount(); for (int i = 0, w = 0; i < cnt; ++i) { w += rowHeights_[static_cast(i)]; if (pos.y() < w) { hovRow_ = i; break; } } } if (hovRow_ != oldRow || hovCol_ != oldCol) ui->panel->update(); } void VisualizedInstrumentMacroEditor::leaveEventInView() { hovRow_ = -1; hovCol_ = -1; ui->panel->update(); } void VisualizedInstrumentMacroEditor::wheelEventInView(QWheelEvent* event) { if (!cols_.size()) return; Ui::EventGuard eg(isIgnoreEvent_); int degree = - event->angleDelta().y() / 8; int pos = ui->verticalScrollBar->value() + degree / 15; int labCnt = static_cast(labels_.size()); if (0 > pos) pos = 0; else if (pos > labCnt - maxDispRowCnt_) pos = labCnt - maxDispRowCnt_; scrollUp(ui->verticalScrollBar->maximum() - pos); ui->panel->update(); ui->verticalScrollBar->setValue(pos); } void VisualizedInstrumentMacroEditor::on_colIncrToolButton_clicked() { addSequenceData(defaultRow_); } void VisualizedInstrumentMacroEditor::on_colDecrToolButton_clicked() { removeSequenceData(); } void VisualizedInstrumentMacroEditor::on_verticalScrollBar_valueChanged(int value) { if (!isIgnoreEvent_) { scrollUp(ui->verticalScrollBar->maximum() - value); ui->panel->update(); } } int VisualizedInstrumentMacroEditor::maxInMML() const { return static_cast(labels_.size()); } void VisualizedInstrumentMacroEditor::on_lineEdit_editingFinished() { interpretMML(); printMML(); } void VisualizedInstrumentMacroEditor::updateColumnWidth() { colWidths_.clear(); if (!cols_.size()) return; float ww = (panelWidth() - labWidth_) / static_cast(cols_.size()); int w = static_cast(ww); float dif = ww - w; float sum = 0; for (size_t i = 0; i < cols_.size(); ++i) { int width = w; sum += dif; if (sum >= 1.0f) { ++width; sum -= 1.0f; } colWidths_.push_back(width); } } void VisualizedInstrumentMacroEditor::updateRowHeight() { rowHeights_.clear(); if (!labels_.size()) return; int div = getDisplayedRowCount(); float hh = (ui->panel->geometry().height() - fontHeight_ * 2) / static_cast(div); int h = static_cast(hh); float dif = hh - h; float sum = 0; for (int i = 0; i < div; ++i) { int height = h; sum += dif; if (sum >= 1.0f) { ++height; sum -= 1.0f; } rowHeights_.push_back(height); } } BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/visualized_instrument_macro_editor.hpp000066400000000000000000000135041476276175200332450ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef VISUALIZED_INSTRUMENT_MACRO_EDITOR_HPP #define VISUALIZED_INSTRUMENT_MACRO_EDITOR_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "instrument/sequence_property.hpp" #include "gui/color_palette.hpp" namespace Ui { class VisualizedInstrumentMacroEditor; } class VisualizedInstrumentMacroEditor : public QWidget { Q_OBJECT public: explicit VisualizedInstrumentMacroEditor(QWidget *parent = nullptr); ~VisualizedInstrumentMacroEditor() override; void setColorPalette(std::shared_ptr palette); void AddRow(QString label = "", bool fitLabelWidth = true); void setMaximumDisplayedRowCount(int count); void setDefaultRow(int row); int getSequenceLength() const; void setSequenceData(int row, int col, QString str = "", int subdata = -1); void setText(int col, QString text); void setSubdata(int col, int subdata); int getSequenceAt(int col) const; int getSequenceDataAt(int col) const; void setMultipleReleaseState(bool enabled); void addSequenceData(int row, QString str = "", int subdata = -1); void removeSequenceData(); void clearSequenceData(); // NOTE: It is desirable to use loop class void addLoop(int begin, int end, int times); void setSequenceType(SequenceType type); enum PermittedReleaseFlag : int { NO_RELEASE = 0, FIXED_RELEASE = 1, ABSOLUTE_RELEASE = 2, RELATIVE_RELEASE = 4 }; void setPermittedReleaseTypes(int types); void setRelease(const InstrumentSequenceRelease& release); void clearData(); void clearRow(); void setUpperRow(int row); void setLabel(int row, QString text); void clearAllLabelText(); void setLabelDiaplayMode(bool isOmitted); void autoFitLabelWidth(); void setMMLDisplay0As(int n); signals: void sequenceDataChanged(int row, int col); void sequenceDataAdded(int row, int col); void sequenceDataRemoved(); void loopCleared(); void loopAdded(InstrumentSequenceLoop loop); void loopRemoved(int begin, int end); void loopChanged(int prevBegin, int prevEnd, InstrumentSequenceLoop loop); void releaseChanged(InstrumentSequenceRelease release); protected: bool eventFilter(QObject*object, QEvent* event) override; void paintEventInView(QPaintEvent* event); void resizeEventInView(QResizeEvent* event); void mousePressEventInView(QMouseEvent* event); void mouseReleaseEventInView(QMouseEvent* event); void mouseMoveEventInView(); void mouseHoverdEventInView(QHoverEvent* event); void leaveEventInView(); void wheelEventInView(QWheelEvent* event); QPixmap pixmap_; std::shared_ptr palette_; QFont font_; QFontMetrics met_; int fontWidth_, fontHeight_, fontAscend_, fontLeading_; int labWidth_; std::vector rowHeights_, colWidths_; int rowHeight_; int fieldHeight_; int maxDispRowCnt_; int upperRow_, defaultRow_; int hovRow_, hovCol_; virtual void drawField(); virtual void updateLabels(); SequenceType type_; struct Column { int row, data; QString text; }; std::vector labels_; std::vector cols_; int permittedReleaseType_; bool isLabelOmitted_; virtual int detectRowNumberForMouseEvent(int col, int internalRow) const; // NOTE: It is desirable to use loop class struct Loop { int begin, end, times; }; std::vector loops_; InstrumentSequenceRelease release_; void printMML(); virtual QString convertSequenceDataUnitToMML(Column col); virtual int maxInMML() const; void interpretMML(); bool interpretSlopeInMML(QString& text, int& cnt, std::vector& column); virtual bool interpretDataInMML(QString& text, int& cnt, std::vector& column); int panelWidth() const; inline int getDisplayedRowCount() const { int labCnt = static_cast(labels_.size()); return (maxDispRowCnt_ > labCnt) ? labCnt : maxDispRowCnt_; } private slots: void on_colIncrToolButton_clicked(); void on_colDecrToolButton_clicked(); void on_verticalScrollBar_valueChanged(int value); void on_lineEdit_editingFinished(); private: Ui::VisualizedInstrumentMacroEditor *ui; int pressRow_, pressCol_; int prevPressRow_, prevPressCol_; int loopY_, releaseY_; int loopBaseY_, releaseBaseY_; int grabLoop_; bool isGrabLoopHead_; bool isGrabRelease_; bool isMultiReleaseState_; int mmlBase_; bool isIgnoreEvent_; void initDisplay(); void drawLoop(); void drawRelease(); void drawBorder(); void drawShadow(); int checkLoopRegion(int col); void moveLoop(); void updateColumnWidth(); void updateRowHeight(); inline void scrollUp(int pos) { upperRow_ = pos - 1 + getDisplayedRowCount(); } }; #endif // VISUALIZED_INSTRUMENT_MACRO_EDITOR_HPP BambooTracker-0.6.5/BambooTracker/gui/instrument_editor/visualized_instrument_macro_editor.ui000066400000000000000000000040431476276175200330710ustar00rootroot00000000000000 VisualizedInstrumentMacroEditor 0 0 400 300 Form 0 0 0 0 0 Qt::Vertical false true - + Size: 1 BambooTracker-0.6.5/BambooTracker/gui/instrument_selection_dialog.cpp000066400000000000000000000105431476276175200260600ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "instrument_selection_dialog.hpp" #include "ui_instrument_selection_dialog.h" #include #include #include "bank.hpp" #include "gui/jam_layout.hpp" InstrumentSelectionDialog::InstrumentSelectionDialog(const AbstractBank &bank, const QString &text, std::weak_ptr config, QWidget *parent) : QDialog(parent), bank_(bank), config_(config), ui_(new Ui::InstrumentSelectionDialog) { ui_->setupUi(this); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); ui_->label->setText(text); setupContents(); ui_->listWidget->installEventFilter(this); } InstrumentSelectionDialog::~InstrumentSelectionDialog() { } void InstrumentSelectionDialog::setupContents() { QListWidget* lw = ui_->listWidget; lw->setSelectionMode(QListWidget::MultiSelection); size_t instCount = bank_.getNumInstruments(); for (size_t i = 0; i < instCount; ++i) { QString id = QString::fromStdString(bank_.getInstrumentIdentifier(i)); QString name = QString::fromStdString(bank_.getInstrumentName(i)); QListWidgetItem* item = new QListWidgetItem(QString("%1 %2").arg(id, name)); item->setData(Qt::UserRole, static_cast(i)); lw->addItem(item); } } QVector InstrumentSelectionDialog::currentInstrumentSelection() const { QListWidget *lw = ui_->listWidget; const QList items = lw->selectedItems(); QVector selection; selection.reserve(items.size()); for (const QListWidgetItem* item : items) { size_t index = static_cast(item->data(Qt::UserRole).toULongLong()); selection.push_back(index); } return selection; } void InstrumentSelectionDialog::onJamKeyOnByMidi(int key) { auto item = ui_->listWidget->currentItem(); if (item) emit jamKeyOnMidiEvent(static_cast(item->data(Qt::UserRole).toULongLong()), key); } void InstrumentSelectionDialog::onJamKeyOffByMidi(int key) { auto item = ui_->listWidget->currentItem(); if (item) emit jamKeyOffMidiEvent(key); } bool InstrumentSelectionDialog::eventFilter(QObject* watched, QEvent* event) { Q_UNUSED(watched) // Only list widget auto item = ui_->listWidget->currentItem(); if (!item) return false; size_t n = static_cast(item->data(Qt::UserRole).toULongLong()); if (event->type() == QEvent::KeyPress) { auto ke = reinterpret_cast(event); if (!ke->isAutoRepeat()) { Qt::Key qtKey = static_cast(ke->key()); try { JamKey jk = getJamKeyFromLayoutMapping(qtKey, config_); emit jamKeyOnEvent(n, jk); } catch (std::invalid_argument&) {} } } else if (event->type() == QEvent::KeyRelease) { auto ke = reinterpret_cast(event); if (!ke->isAutoRepeat()) { Qt::Key qtKey = static_cast(ke->key()); try { JamKey jk = getJamKeyFromLayoutMapping(qtKey, config_); emit jamKeyOffEvent(jk); } catch (std::invalid_argument&) {} } } return false; } void InstrumentSelectionDialog::on_searchLineEdit_textChanged(const QString &search) { QListWidget *lw = ui_->listWidget; unsigned count = static_cast(lw->count()); for (unsigned row = 0; row < count; ++row) { QListWidgetItem *item = lw->item(static_cast(row)); bool accept = search.isEmpty() || item->text().contains(search, Qt::CaseInsensitive); item->setHidden(!accept); } } BambooTracker-0.6.5/BambooTracker/gui/instrument_selection_dialog.hpp000066400000000000000000000041671476276175200260720ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include "configuration.hpp" class AbstractBank; namespace Ui { class InstrumentSelectionDialog; } class InstrumentSelectionDialog : public QDialog { Q_OBJECT public: InstrumentSelectionDialog(const AbstractBank &bank, const QString &text, std::weak_ptr config, QWidget *parent = nullptr); ~InstrumentSelectionDialog() override; QVector currentInstrumentSelection() const; public slots: void onJamKeyOnByMidi(int key); void onJamKeyOffByMidi(int key); signals: void jamKeyOnEvent(size_t id, JamKey key); void jamKeyOnMidiEvent(size_t id, int key); void jamKeyOffEvent(JamKey key); void jamKeyOffMidiEvent(int key); protected: bool eventFilter(QObject* watched, QEvent* event) override; private: const AbstractBank &bank_; std::weak_ptr config_; std::unique_ptr ui_; void setupContents(); private slots: void on_searchLineEdit_textChanged(const QString &search); }; BambooTracker-0.6.5/BambooTracker/gui/instrument_selection_dialog.ui000066400000000000000000000035251476276175200257150ustar00rootroot00000000000000 InstrumentSelectionDialog 0 0 400 300 Select instruments Search... Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() InstrumentSelectionDialog accept() 248 254 157 274 buttonBox rejected() InstrumentSelectionDialog reject() 316 260 286 274 BambooTracker-0.6.5/BambooTracker/gui/jam_layout.hpp000066400000000000000000000044121476276175200224330ustar00rootroot00000000000000/* * Copyright (C) 2020-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef JAM_LAYOUT_HPP #define JAM_LAYOUT_HPP #include #include #include #include "configuration.hpp" #include "jamming.hpp" #include "utils.hpp" // Layout decipherer inline JamKey getJamKeyFromLayoutMapping(Qt::Key key, std::weak_ptr config) { std::shared_ptr configLocked = config.lock(); Configuration::KeyboardLayout selectedLayout = configLocked->getNoteEntryLayout(); if (configLocked->mappingLayouts.find(selectedLayout) != configLocked->mappingLayouts.end()) { std::unordered_map selectedLayoutMapping = configLocked->mappingLayouts.at(selectedLayout); auto it = utils::findIf(selectedLayoutMapping, [key](const std::pair& t) -> bool { return (QKeySequence(key).matches(QKeySequence(QString::fromStdString(t.first))) == QKeySequence::ExactMatch); }); if (it != selectedLayoutMapping.end()) { return it->second; } else { throw std::invalid_argument("Unmapped key"); } //something has gone wrong, current layout has no layout map //TODO: handle cleanly? } else throw std::out_of_range("Unmapped Layout"); } #endif // JAM_LAYOUT_HPP BambooTracker-0.6.5/BambooTracker/gui/key_signature_manager_form.cpp000066400000000000000000000140751476276175200256560ustar00rootroot00000000000000/* * Copyright (C) 2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "key_signature_manager_form.hpp" #include "ui_key_signature_manager_form.h" #include "gui/note_name_manager.hpp" KeySignatureManagerForm::KeySignatureManagerForm(std::weak_ptr core, bool showHex, QWidget *parent) : QWidget(parent), ui(new Ui::KeySignatureManagerForm), bt_(core), curSong_(core.lock()->getCurrentSongNumber()) { ui->setupUi(this); setWindowFlags((windowFlags() & ~(Qt::WindowContextHelpButtonHint | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint)) | Qt::WindowStaysOnTopHint); for (int i = KeySignature::FISRT; i <= KeySignature::LAST; ++i) { ui->keyComboBox->addItem(NoteNameManager::getManager().getKeyName(static_cast(i)), i); } setNumberSettings(showHex); initList(); ui->orderSpinBox->setDisplayIntegerBase(numBase_); ui->stepSpinBox->setDisplayIntegerBase(numBase_); insSc_ = std::make_unique(Qt::Key_Insert, ui->listWidget, nullptr, nullptr, Qt::WidgetShortcut); QObject::connect(insSc_.get(), &QShortcut::activated, this, &KeySignatureManagerForm::on_createPushButton_clicked); delSc_ = std::make_unique(Qt::Key_Delete, ui->listWidget, nullptr, nullptr, Qt::WidgetShortcut); QObject::connect(delSc_.get(), &QShortcut::activated, this, &KeySignatureManagerForm::on_removePushButton_clicked); } KeySignatureManagerForm::~KeySignatureManagerForm() { delete ui; } void KeySignatureManagerForm::initList() { int size = static_cast(bt_.lock()->getKeySignatureSize(curSong_)); for (int i = 0; i < size; ++i) { KeySignature ks = bt_.lock()->getKeySignature(curSong_, i); addKeySignature(ks.type, ks.order, ks.step, true); } } void KeySignatureManagerForm::addKeySignature(KeySignature::Type key, int order, int step, bool onlyUi) { if (onlyUi) { ui->listWidget->addItem(createText(key, order, step)); } else { bt_.lock()->addKeySignature(curSong_, key, order, step); ui->listWidget->clear(); initList(); } } void KeySignatureManagerForm::removeKeySignature(int i) { if (!i) return; delete ui->listWidget->takeItem(i); bt_.lock()->removeKeySignature(curSong_, i); } QString KeySignatureManagerForm::createText(KeySignature::Type key, int order, int step) { auto pos = QString("%1,%2").arg(order, numWidth_, numBase_, QChar('0')) .arg(step, numWidth_, numBase_, QChar('0')).toUpper(); return (pos + ": " + NoteNameManager::getManager().getKeyName(key)); } void KeySignatureManagerForm::onCurrentSongNumberChanged() { curSong_ = bt_.lock()->getCurrentSongNumber(); ui->listWidget->clear(); initList(); } void KeySignatureManagerForm::onConfigurationChanged(bool showHex) { for (int i = KeySignature::FISRT; i <= KeySignature::LAST; ++i) { ui->keyComboBox->setItemText(i, NoteNameManager::getManager().getKeyName(static_cast(i))); } setNumberSettings(showHex); for (int i = 0; i < ui->listWidget->count(); ++i) { KeySignature ks = bt_.lock()->getKeySignature(curSong_, i); ui->listWidget->item(i)->setText(createText(ks.type, ks.order, ks.step)); } ui->orderSpinBox->setDisplayIntegerBase(numBase_); ui->stepSpinBox->setDisplayIntegerBase(numBase_); } void KeySignatureManagerForm::on_createPushButton_clicked() { if (bt_.lock()->getKeySignatureSize(curSong_) == 127) return; // Maximum size addKeySignature(static_cast(ui->keyComboBox->currentData().toInt()), ui->orderSpinBox->value(), ui->stepSpinBox->value()); emit modified(); } void KeySignatureManagerForm::on_updatePushButton_clicked() { int row = ui->listWidget->currentRow(); if (row == -1) return; auto key = static_cast(ui->keyComboBox->currentData().toInt()); int order = ui->orderSpinBox->value(); int step = ui->stepSpinBox->value(); if (!row && (order || step)) bt_.lock()->addKeySignature(curSong_, key, order, step); else bt_.lock()->changeKeySignature(curSong_, row, key, order, step); ui->listWidget->clear(); initList(); emit modified(); } void KeySignatureManagerForm::on_removePushButton_clicked() { int row = ui->listWidget->currentRow(); if (row < 2) return; removeKeySignature(row); emit modified(); } void KeySignatureManagerForm::on_clearAllPushButton_clicked() { if (!ui->listWidget->count()) return; ui->listWidget->clear(); bt_.lock()->clearKeySignature(curSong_); initList(); emit modified(); } void KeySignatureManagerForm::on_listWidget_currentRowChanged(int currentRow) { if (currentRow == -1) return; KeySignature ks = bt_.lock()->getKeySignature(curSong_, currentRow); for (int i = 0; i < ui->keyComboBox->count(); ++i) { if (static_cast(ui->keyComboBox->itemData(i).toInt()) == ks.type) { ui->keyComboBox->setCurrentIndex(i); break; } } ui->orderSpinBox->setValue(ks.order); ui->stepSpinBox->setValue(ks.step); } void KeySignatureManagerForm::on_listWidget_itemDoubleClicked(QListWidgetItem* item) { int row = ui->listWidget->row(item); KeySignature ks = bt_.lock()->getKeySignature(curSong_, row); emit positionJumpRequested(ks.order, ks.step); } BambooTracker-0.6.5/BambooTracker/gui/key_signature_manager_form.hpp000066400000000000000000000051341476276175200256570ustar00rootroot00000000000000/* * Copyright (C) 2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef KEY_SIGNATURE_MANAGER_FORM_HPP #define KEY_SIGNATURE_MANAGER_FORM_HPP #include #include #include #include #include #include "bamboo_tracker.hpp" #include "song.hpp" namespace Ui { class KeySignatureManagerForm; } class KeySignatureManagerForm : public QWidget { Q_OBJECT public: KeySignatureManagerForm(std::weak_ptr core, bool showHex, QWidget *parent = nullptr); ~KeySignatureManagerForm() override; public slots: void onCurrentSongNumberChanged(); void onConfigurationChanged(bool showHex); signals: void positionJumpRequested(int order, int step); void modified(); private slots: void on_createPushButton_clicked(); void on_updatePushButton_clicked(); void on_removePushButton_clicked(); void on_clearAllPushButton_clicked(); void on_listWidget_currentRowChanged(int currentRow); void on_listWidget_itemDoubleClicked(QListWidgetItem* item); private: Ui::KeySignatureManagerForm *ui; std::weak_ptr bt_; int curSong_; int numWidth_, numBase_; std::unique_ptr insSc_, delSc_; void initList(); inline void setNumberSettings(bool showHex) { if (showHex) { numWidth_ = 2; numBase_ = 16; } else { numWidth_ = 3; numBase_ = 10; } } void addKeySignature(KeySignature::Type key, int order, int step, bool onlyUi = false); void removeKeySignature(int i); QString createText(KeySignature::Type key, int order, int step); }; #endif // KEY_SIGNATURE_MANAGER_FORM_HPP BambooTracker-0.6.5/BambooTracker/gui/key_signature_manager_form.ui000066400000000000000000000064641476276175200255140ustar00rootroot00000000000000 KeySignatureManagerForm 0 0 320 289 Key Signature Manager Key signature editor Key Order Step Create New Update Remove Clear All Qt::Vertical 20 40 listWidget removePushButton clearAllPushButton keyComboBox orderSpinBox stepSpinBox createPushButton updatePushButton BambooTracker-0.6.5/BambooTracker/gui/keyboard_shortcut_list_dialog.cpp000066400000000000000000000027471476276175200264000ustar00rootroot00000000000000/* * Copyright (C) 2019 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "keyboard_shortcut_list_dialog.hpp" #include "ui_keyboard_shortcut_list_dialog.h" KeyboardShortcutListDialog::KeyboardShortcutListDialog(QWidget *parent) : QDialog(parent), ui(new Ui::KeyboardShortcutListDialog) { ui->setupUi(this); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); } KeyboardShortcutListDialog::~KeyboardShortcutListDialog() { delete ui; } BambooTracker-0.6.5/BambooTracker/gui/keyboard_shortcut_list_dialog.hpp000066400000000000000000000030061476276175200263720ustar00rootroot00000000000000/* * Copyright (C) 2019 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef KEYBOARD_SHORTCUT_LIST_DIALOG_HPP #define KEYBOARD_SHORTCUT_LIST_DIALOG_HPP #include namespace Ui { class KeyboardShortcutListDialog; } class KeyboardShortcutListDialog : public QDialog { Q_OBJECT public: explicit KeyboardShortcutListDialog(QWidget *parent = nullptr); ~KeyboardShortcutListDialog(); private: Ui::KeyboardShortcutListDialog *ui; }; #endif // KEYBOARD_SHORTCUT_LIST_DIALOG_HPP BambooTracker-0.6.5/BambooTracker/gui/keyboard_shortcut_list_dialog.ui000066400000000000000000000032131476276175200262200ustar00rootroot00000000000000 KeyboardShortcutListDialog 0 0 400 300 Keyboard shortcuts qrc:/doc/shortcuts Qt::Horizontal QDialogButtonBox::Close buttonBox accepted() KeyboardShortcutListDialog accept() 248 254 157 274 buttonBox rejected() KeyboardShortcutListDialog reject() 316 260 286 274 BambooTracker-0.6.5/BambooTracker/gui/labeled_horizontal_slider.cpp000066400000000000000000000100351476276175200254630ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "labeled_horizontal_slider.hpp" #include "ui_labeled_horizontal_slider.h" #include #include "slider_style.hpp" LabeledHorizontalSlider::LabeledHorizontalSlider(QWidget *parent) : LabeledHorizontalSlider("", "", "", parent) {} LabeledHorizontalSlider::LabeledHorizontalSlider(QString text, QString prefix, QString suffix, QWidget *parent) : QFrame(parent), ui(new Ui::LabeledHorizontalSlider) { ui->setupUi(this); rate_ = 1.0; precision_ = 0; isSigned_ = false; ui->textLabel->setText(text); prefix_ = prefix; suffix_ = suffix; updateValueLabel(); ui->slider->setStyle(SliderStyle::instance()); ui->slider->installEventFilter(this); } LabeledHorizontalSlider::~LabeledHorizontalSlider() { delete ui; } int LabeledHorizontalSlider::value() const { return ui->slider->value(); } void LabeledHorizontalSlider::setValue(int value) { ui->slider->setValue(value); } int LabeledHorizontalSlider::maximum() const { return ui->slider->maximum(); } void LabeledHorizontalSlider::setMaximum(int value) { ui->slider->setMaximum(value); } int LabeledHorizontalSlider::minimum() const { return ui->slider->minimum(); } void LabeledHorizontalSlider::setMinimum(int value) { ui->slider->setMinimum(value); } void LabeledHorizontalSlider::setValueRate(double rate, int precision) { rate_ = rate; precision_ = precision; updateValueLabel(); } void LabeledHorizontalSlider::setSign(bool enabled) { isSigned_ = enabled; updateValueLabel(); } void LabeledHorizontalSlider::setTickPosition(QSlider::TickPosition position) { ui->slider->setTickPosition(position); } void LabeledHorizontalSlider::setTickInterval(int ti) { ui->slider->setTickInterval(ti); } QString LabeledHorizontalSlider::text() const { return ui->textLabel->text(); } void LabeledHorizontalSlider::setText(QString text) { ui->textLabel->setText(text); } QString LabeledHorizontalSlider::suffix() const { return suffix_; } void LabeledHorizontalSlider::setSuffix(QString suffix) { suffix_ = suffix; updateValueLabel(); } QString LabeledHorizontalSlider::prefix() const { return prefix_; } void LabeledHorizontalSlider::setprefix(QString prefix) { prefix_ = prefix; updateValueLabel(); } bool LabeledHorizontalSlider::eventFilter(QObject* watched, QEvent* event) { if (watched == ui->slider) { if (event->type() == QEvent::Wheel) { auto e = dynamic_cast(event); if (e->angleDelta().y() > 0) ui->slider->setValue(ui->slider->value() + 1); else if (e->angleDelta().y() < 0) ui->slider->setValue(ui->slider->value() - 1); return true; } } return QFrame::eventFilter(watched, event); } void LabeledHorizontalSlider::on_slider_valueChanged(int value) { updateValueLabel(); emit valueChanged(value); } void LabeledHorizontalSlider::updateValueLabel() { QString sign = (isSigned_ && ui->slider->value() > -1) ? "+" : ""; ui->valueLabel->setText( prefix_ + sign + QString::number(ui->slider->value() * rate_, 'f', precision_) + suffix_); } BambooTracker-0.6.5/BambooTracker/gui/labeled_horizontal_slider.hpp000066400000000000000000000046271476276175200255020ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef LABALED_HORIZONTAL_SLIDER_HPP #define LABALED_HORIZONTAL_SLIDER_HPP #include #include #include #include namespace Ui { class LabeledHorizontalSlider; } class LabeledHorizontalSlider : public QFrame { Q_OBJECT public: explicit LabeledHorizontalSlider(QWidget *parent = nullptr); LabeledHorizontalSlider(QString text, QString prefix = "", QString suffix = "", QWidget *parent = nullptr); ~LabeledHorizontalSlider() override; int value() const; void setValue(int value); int maximum() const; void setMaximum(int value); int minimum() const; void setMinimum(int value); void setValueRate(double rate, int precision = 1); void setSign(bool enabled); void setTickPosition(QSlider::TickPosition position); void setTickInterval(int ti); QString text() const; void setText(QString text); QString suffix() const; void setSuffix(QString suffix); QString prefix() const; void setprefix(QString prefix); signals: void valueChanged(int value); protected: bool eventFilter(QObject* watched, QEvent* event) override; private slots: void on_slider_valueChanged(int value); private: Ui::LabeledHorizontalSlider *ui; QString suffix_, prefix_; double rate_; int precision_; bool isSigned_; void updateValueLabel(); }; #endif // LABALED_HORIZONTAL_SLIDER_HPP BambooTracker-0.6.5/BambooTracker/gui/labeled_horizontal_slider.ui000066400000000000000000000027411476276175200253230ustar00rootroot00000000000000 LabeledHorizontalSlider 0 0 136 28 Frame LabeledHorizontalSlider { border: 0; } 3 0 0 0 0 Text Qt::Horizontal 0 BambooTracker-0.6.5/BambooTracker/gui/labeled_vertical_slider.cpp000066400000000000000000000077431476276175200251170ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "labeled_vertical_slider.hpp" #include "ui_labeled_vertical_slider.h" #include #include "slider_style.hpp" LabeledVerticalSlider::LabeledVerticalSlider(QWidget *parent) : LabeledVerticalSlider("", "", "", parent) {} LabeledVerticalSlider::LabeledVerticalSlider(QString text, QString prefix, QString suffix, QWidget *parent) : QFrame(parent), ui(new Ui::LabeledVerticalSlider) { ui->setupUi(this); rate_ = 1.0; precision_ = 0; isSigned_ = false; ui->textLabel->setText(text); prefix_ = prefix; suffix_ = suffix; updateValueLabel(); ui->slider->setStyle(SliderStyle::instance()); ui->slider->installEventFilter(this); } LabeledVerticalSlider::~LabeledVerticalSlider() { delete ui; } int LabeledVerticalSlider::value() const { return ui->slider->value(); } void LabeledVerticalSlider::setValue(int value) { ui->slider->setValue(value); } int LabeledVerticalSlider::maximum() const { return ui->slider->maximum(); } void LabeledVerticalSlider::setMaximum(int value) { ui->slider->setMaximum(value); } int LabeledVerticalSlider::minimum() const { return ui->slider->minimum(); } void LabeledVerticalSlider::setMinimum(int value) { ui->slider->setMinimum(value); } void LabeledVerticalSlider::setValueRate(double rate, int precision) { rate_ = rate; precision_ = precision; updateValueLabel(); } void LabeledVerticalSlider::setSign(bool enabled) { isSigned_ = enabled; updateValueLabel(); } void LabeledVerticalSlider::setTickPosition(QSlider::TickPosition position) { ui->slider->setTickPosition(position); } void LabeledVerticalSlider::setTickInterval(int ti) { ui->slider->setTickInterval(ti); } QString LabeledVerticalSlider::text() const { return ui->textLabel->text(); } void LabeledVerticalSlider::setText(QString text) { ui->textLabel->setText(text); } QString LabeledVerticalSlider::suffix() const { return suffix_; } void LabeledVerticalSlider::setSuffix(QString suffix) { suffix_ = suffix; updateValueLabel(); } QString LabeledVerticalSlider::prefix() const { return prefix_; } void LabeledVerticalSlider::setprefix(QString prefix) { prefix_ = prefix; updateValueLabel(); } bool LabeledVerticalSlider::eventFilter(QObject* watched, QEvent* event) { if (watched == ui->slider) { if (event->type() == QEvent::Wheel) { auto e = dynamic_cast(event); if (e->angleDelta().y() > 0) ui->slider->setValue(ui->slider->value() + 1); else if (e->angleDelta().y() < 0) ui->slider->setValue(ui->slider->value() - 1); return true; } } return QFrame::eventFilter(watched, event); } void LabeledVerticalSlider::on_slider_valueChanged(int value) { updateValueLabel(); emit valueChanged(value); } void LabeledVerticalSlider::updateValueLabel() { QString sign = (isSigned_ && ui->slider->value() > -1) ? "+" : ""; ui->valueLabel->setText( prefix_ + sign + QString::number(ui->slider->value() * rate_, 'f', precision_) + suffix_); } BambooTracker-0.6.5/BambooTracker/gui/labeled_vertical_slider.hpp000066400000000000000000000046051476276175200251160ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef LABELED_VERTICAL_SLIDER_HPP #define LABELED_VERTICAL_SLIDER_HPP #include #include #include #include namespace Ui { class LabeledVerticalSlider; } class LabeledVerticalSlider : public QFrame { Q_OBJECT public: explicit LabeledVerticalSlider(QWidget *parent = nullptr); LabeledVerticalSlider(QString text, QString prefix = "", QString suffix = "", QWidget *parent = nullptr); ~LabeledVerticalSlider() override; int value() const; void setValue(int value); int maximum() const; void setMaximum(int value); int minimum() const; void setMinimum(int value); void setValueRate(double rate, int precision = 1); void setSign(bool enabled); void setTickPosition(QSlider::TickPosition position); void setTickInterval(int ti); QString text() const; void setText(QString text); QString suffix() const; void setSuffix(QString suffix); QString prefix() const; void setprefix(QString prefix); signals: void valueChanged(int value); protected: bool eventFilter(QObject* watched, QEvent* event) override; private slots: void on_slider_valueChanged(int value); private: Ui::LabeledVerticalSlider *ui; QString suffix_, prefix_; double rate_; int precision_; bool isSigned_; void updateValueLabel(); }; #endif // LABELED_VERTICAL_SLIDER_HPP BambooTracker-0.6.5/BambooTracker/gui/labeled_vertical_slider.ui000066400000000000000000000031401476276175200247350ustar00rootroot00000000000000 LabeledVerticalSlider 0 0 36 115 Frame LabeledVerticalSlider { border: 0; } 3 0 0 0 0 Text Qt::AlignCenter Qt::Vertical 0 Qt::AlignCenter BambooTracker-0.6.5/BambooTracker/gui/mainwindow.cpp000066400000000000000000004313711476276175200224460ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "mainwindow.hpp" #include "ui_mainwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "jamming.hpp" #include "song.hpp" #include "track.hpp" #include "instrument.hpp" #include "bank.hpp" #include "io/io_file_type.hpp" #include "io/module_io.hpp" #include "io/instrument_io.hpp" #include "io/bank_io.hpp" #include "io/binary_container.hpp" #include "io/wav_container.hpp" #include "version.hpp" #include "gui/command/instrument/instrument_commands_qt.hpp" #include "gui/instrument_editor/fm_instrument_editor.hpp" #include "gui/instrument_editor/ssg_instrument_editor.hpp" #include "gui/instrument_editor/adpcm_instrument_editor.hpp" #include "gui/instrument_editor/adpcm_drumkit_editor.hpp" #include "gui/module_properties_dialog.hpp" #include "gui/groove_settings_dialog.hpp" #include "gui/configuration_dialog.hpp" #include "gui/wave_export_settings_dialog.hpp" #include "gui/vgm_export_settings_dialog.hpp" #include "gui/instrument_selection_dialog.hpp" #include "gui/s98_export_settings_dialog.hpp" #include "gui/configuration_handler.hpp" #include "gui/jam_layout.hpp" #include "gui/wheel_spin_box.hpp" #include "chip/scci/scci_wrapper.hpp" #include "chip/c86ctl/c86ctl_wrapper.hpp" #include "midi/midi.hpp" #include "audio/audio_stream_rtaudio.hpp" #include "enum_hash.hpp" #include "gui/go_to_dialog.hpp" #include "gui/transpose_song_dialog.hpp" #include "gui/swap_tracks_dialog.hpp" #include "gui/hide_tracks_dialog.hpp" #include "gui/file_io_error_message_box.hpp" #include "gui/note_name_manager.hpp" #include "gui/gui_utils.hpp" #include "gui/command_result_message_box.hpp" #include "utils.hpp" namespace { const std::unordered_map TB_POS_ = { { Configuration::ToolbarPosition::TopPosition, Qt::TopToolBarArea }, { Configuration::ToolbarPosition::BottomPosition, Qt::BottomToolBarArea }, { Configuration::ToolbarPosition::LeftPosition, Qt::LeftToolBarArea }, { Configuration::ToolbarPosition::RightPosition, Qt::RightToolBarArea } }; constexpr int STATUS_DISPLAY_TIMEOUT = 0; } ModuleSaveCheckDialog::ModuleSaveCheckDialog(const std::string& name, QWidget* parent) : QMessageBox(QMessageBox::Warning, "BambooTracker", "", QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, parent) { setText(tr("Save changes to %1?").arg(name.empty() ? tr("Untitled") : gui_utils::utf8ToQString(name))); } MainWindow::MainWindow(std::weak_ptr config, QString filePath, bool isFirstLaunch, QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), config_(config), palette_(std::make_shared()), bt_(std::make_shared(config)), comStack_(std::make_shared(this)), fileHistory_(std::make_shared()), scciDll_(std::make_shared("scci")), c86ctlDll_(std::make_shared("c86ctl")), instDialogMan_(std::make_shared()), renamingInstItem_(nullptr), renamingInstEdit_(nullptr), isModifiedForNotCommand_(false), hasLockedWigets_(false), isEditedPattern_(true), isEditedOrder_(false), isEditedInstList_(false), isSelectedPattern_(false), isSelectedOrder_(false), hasShownOnce_(false), firstViewUpdateRequest_(false), octUpSc_(nullptr), octDownSc_(nullptr), focusPtnSc_(this), focusOdrSc_(this), focusInstSc_(this), playAndStopSc_(nullptr), playStepSc_(nullptr), goPrevOdrSc_(nullptr), goNextOdrSc_(nullptr), prevInstSc_(nullptr), nextInstSc_(nullptr), incPtnSizeSc_(nullptr), decPtnSizeSc_(nullptr), incEditStepSc_(nullptr), decEditStepSc_(nullptr), prevSongSc_(nullptr), nextSongSc_(nullptr), jamVolUpSc_(nullptr), jamVolDownSc_(nullptr), bankJamMidiCtrl_(false) { ui->setupUi(this); if (config.lock()->getMainWindowX() == -1) { // When unset QRect rec = geometry(); rec.moveCenter(QGuiApplication::screens().at(0)->geometry().center()); setGeometry(rec); config.lock()->setMainWindowX(x()); config.lock()->setMainWindowY(y()); } else { move(config.lock()->getMainWindowX(), config.lock()->getMainWindowY()); } if (isFirstLaunch) { const QHash defNoteSysMap = { { "English", NoteNotationSystem::ENGLISH }, { "German", NoteNotationSystem::GERMAN } }; //: Set the name of suitable notation system (English or German) config.lock()->setNotationSystem(defNoteSysMap[tr("English", "Default notation system")]); } resize(config.lock()->getMainWindowWidth(), config.lock()->getMainWindowHeight()); if (config.lock()->getMainWindowMaximized()) showMaximized(); ui->action_Status_Bar->setChecked(config.lock()->getVisibleStatusBar()); ui->statusBar->setVisible(config.lock()->getVisibleStatusBar()); ui->actionFollow_Mode->setChecked(config.lock()->getFollowMode()); ui->action_Instrument_Mask->setChecked(config.lock()->getInstrumentMask()); ui->action_Volume_Mask->setChecked(config.lock()->getVolumeMask()); ui->action_Wave_View->setChecked(config.lock()->getVisibleWaveView()); ui->waveVisual->setVisible(config.lock()->getVisibleWaveView()); bt_->setFollowPlay(config.lock()->getFollowMode()); NoteNameManager::getManager().setNotationSystem(config.lock()->getNotationSystem()); ui->patternEditor->setConfiguration(config_.lock()); ui->orderList->setConfiguration(config_.lock()); updateFonts(); ui->orderList->setHorizontalScrollMode(config.lock()->getMoveCursorByHorizontalScroll(), false); ui->patternEditor->setHorizontalScrollMode(config.lock()->getMoveCursorByHorizontalScroll(), false); ui->patternEditor->setCore(bt_); ui->orderList->setCore(bt_); io::loadPalette(palette_.get()); ui->patternEditor->setColorPallete(palette_); ui->orderList->setColorPallete(palette_); updateInstrumentListColors(); ui->waveVisual->setColorPalette(palette_); /* Command stack */ QObject::connect(comStack_.get(), &QUndoStack::indexChanged, this, [&](int idx) { setWindowModified(idx || isModifiedForNotCommand_); ui->actionUndo->setEnabled(comStack_->canUndo()); ui->actionRedo->setEnabled(comStack_->canRedo()); }); /* File history */ io::loadFileHistory(fileHistory_); for (size_t i = 0; i < fileHistory_->size(); ++i) { // Leave Before Qt5.7.0 style due to windows xp QAction* action = ui->menu_Recent_Files->addAction(QString("&%1 %2").arg(i + 1).arg(fileHistory_->at(i))); action->setData(fileHistory_->at(i)); } QObject::connect(ui->menu_Recent_Files, &QMenu::triggered, this, [&](QAction* action) { if (action != ui->actionClear) { if (isWindowModified()) { switch (ModuleSaveCheckDialog(bt_->getModuleTitle(), this).exec()) { case QMessageBox::Yes: if (!on_actionSave_triggered()) return; break; case QMessageBox::No: break; case QMessageBox::Cancel: return; default: break; } } openModule(action->data().toString()); } }); /* Menus */ pasteModeGroup_ = std::make_unique(this); pasteModeGroup_->addAction(ui->action_Cursor); QObject::connect(ui->action_Cursor, &QAction::triggered, this, [&](bool checked) { if (checked) config_.lock()->setPasteMode(Configuration::PasteMode::Cursor); }); pasteModeGroup_->addAction(ui->action_Selection); QObject::connect(ui->action_Selection, &QAction::triggered, this, [&](bool checked) { if (checked) config_.lock()->setPasteMode(Configuration::PasteMode::Selection); }); pasteModeGroup_->addAction(ui->action_Fill); QObject::connect(ui->action_Fill, &QAction::triggered, this, [&](bool checked) { if (checked) config_.lock()->setPasteMode(Configuration::PasteMode::Fill); }); switch (config.lock()->getPasteMode()) { case Configuration::PasteMode::Cursor: ui->action_Cursor->setChecked(true); break; case Configuration::PasteMode::Selection: ui->action_Selection->setChecked(true); break; case Configuration::PasteMode::Fill: ui->action_Fill->setChecked(true); break; } /* Tool bars */ { auto genSB = [](int value, int min, int max, int minW) { auto sb = new WheelSpinBox(); sb->setMinimum(min); sb->setMaximum(max); sb->setValue(value); sb->setMinimumWidth(minW); sb->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); return sb; }; /* Octave */ auto octLab = new QLabel(tr("Octave")); octLab->setMargin(6); ui->subToolBar->addWidget(octLab); octave_ = genSB(bt_->getCurrentOctave(), 0, 7, 40); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(octave_, static_cast(&QSpinBox::valueChanged), this, [&](int octave) { bt_->setCurrentOctave(octave); statusOctave_->setText(tr("Octave: %1").arg(octave)); }); ui->subToolBar->addWidget(octave_); /* Volume */ auto volLab = new QLabel(tr("Volume")); volLab->setMargin(6); ui->subToolBar->addWidget(volLab); volume_ = [value = bt_->getCurrentVolume(), genSB] { auto sb = genSB(value, 0, 255, 40); sb->setDisplayIntegerBase(16); QFont font = sb->font(); font.setCapitalization(QFont::AllUppercase); sb->setFont(font); return sb; }(); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(volume_, static_cast(&QSpinBox::valueChanged), this, [&](int volume) { bt_->setCurrentVolume(volume); }); ui->subToolBar->addWidget(volume_); ui->subToolBar->addSeparator(); /* Follow mode */ ui->subToolBar->addAction(ui->actionFollow_Mode); ui->subToolBar->addSeparator(); /* Step highlight 1st */ auto hlLab1 = new QLabel(tr("Step highlight 1st")); hlLab1->setMargin(6); ui->subToolBar->addWidget(hlLab1); highlight1_ = genSB(8, 1, 256, 50); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(highlight1_, static_cast(&QSpinBox::valueChanged), this, [&](int count) { bt_->setModuleStepHighlight1Distance(static_cast(count)); ui->patternEditor->setPatternHighlight1Count(count); }); ui->subToolBar->addWidget(highlight1_); /* Step highlight 2nd */ auto hlLab2 = new QLabel(tr("2nd")); hlLab2->setMargin(6); ui->subToolBar->addWidget(hlLab2); highlight2_ = genSB(8, 1, 256, 50); highlight2_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(highlight2_, static_cast(&QSpinBox::valueChanged), this, [&](int count) { bt_->setModuleStepHighlight2Distance(static_cast(count)); ui->patternEditor->setPatternHighlight2Count(count); }); ui->subToolBar->addWidget(highlight2_); ui->subToolBar->addSeparator(); auto& mainTbConfig = config.lock()->getMainToolbarConfiguration(); if (mainTbConfig.getPosition() == Configuration::ToolbarPosition::FloatPorition) { ui->mainToolBar->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint); ui->mainToolBar->move(mainTbConfig.getX(), mainTbConfig.getY()); } else { addToolBar(TB_POS_.at(mainTbConfig.getPosition()), ui->mainToolBar); } auto& subTbConfig = config.lock()->getSubToolbarConfiguration(); if (subTbConfig.getPosition() == Configuration::ToolbarPosition::FloatPorition) { ui->subToolBar->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint); ui->subToolBar->move(subTbConfig.getX(), subTbConfig.getY()); } else { auto pos = TB_POS_.at(subTbConfig.getPosition()); if (subTbConfig.getNumber()) { if (subTbConfig.hasBreakBefore()) addToolBarBreak(pos); addToolBar(pos, ui->subToolBar); } else { if (mainTbConfig.getPosition() == subTbConfig.getPosition()) { insertToolBar(ui->mainToolBar, ui->subToolBar); if (mainTbConfig.hasBreakBefore()) insertToolBarBreak(ui->mainToolBar); } else { addToolBar(pos, ui->subToolBar); } } } ui->action_Toolbar->setChecked(config.lock()->getVisibleToolbar()); ui->mainToolBar->setVisible(config.lock()->getVisibleToolbar()); ui->subToolBar->setVisible(config.lock()->getVisibleToolbar()); } /* Splitter */ ui->splitter->setStretchFactor(0, 0); ui->splitter->setStretchFactor(1, 1); /* Module settings */ QObject::connect(ui->modTitleLineEdit, &QLineEdit::textEdited, this, [&](const QString& str) { bt_->setModuleTitle(str.toUtf8().toStdString()); setModifiedTrue(); setWindowTitle(); }); QObject::connect(ui->authorLineEdit, &QLineEdit::textEdited, this, [&](const QString& str) { bt_->setModuleAuthor(str.toUtf8().toStdString()); setModifiedTrue(); }); QObject::connect(ui->copyrightLineEdit, &QLineEdit::textEdited, this, [&](const QString& str) { bt_->setModuleCopyright(str.toUtf8().toStdString()); setModifiedTrue(); }); /* Edit settings */ // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->editableStepSpinBox, static_cast(&QSpinBox::valueChanged), this, [&](int n) { ui->patternEditor->setEditableStep(n); config_.lock()->setEditableStep(static_cast(n)); }); ui->editableStepSpinBox->setValue(static_cast(config.lock()->getEditableStep())); ui->patternEditor->setEditableStep(static_cast(config.lock()->getEditableStep())); ui->keyRepeatCheckBox->setCheckState(config.lock()->getKeyRepetition() ? Qt::Checked : Qt::Unchecked); /* Song */ // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->songComboBox, static_cast(&QComboBox::currentIndexChanged), this, [&](int num) { if (num == -1) return; freezeViews(); if (!tickTimerForRealChip_) { try { stream_->stop(); } catch (std::exception& e) { showStreamFailedDialog(e.what()); } } bt_->setCurrentSongNumber(num); loadSong(); if (!tickTimerForRealChip_) { try { stream_->start(); } catch (std::exception& e) { showStreamFailedDialog(e.what()); } } }); /* Song settings */ // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->tempoSpinBox, static_cast(&QSpinBox::valueChanged), this, [&](int tempo) { int curSong = bt_->getCurrentSongNumber(); if (tempo != bt_->getSongTempo(curSong)) { bt_->setSongTempo(curSong, tempo); setModifiedTrue(); } }); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->speedSpinBox, static_cast(&QSpinBox::valueChanged), this, [&](int speed) { int curSong = bt_->getCurrentSongNumber(); if (speed != bt_->getSongSpeed(curSong)) { bt_->setSongSpeed(curSong, speed); setModifiedTrue(); } }); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->patternSizeSpinBox, static_cast(&QSpinBox::valueChanged), this, [&](int size) { bt_->setDefaultPatternSize(bt_->getCurrentSongNumber(), static_cast(size)); ui->patternEditor->onDefaultPatternSizeChanged(); setModifiedTrue(); }); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->grooveSpinBox, static_cast(&QSpinBox::valueChanged), this, [&](int n) { bt_->setSongGroove(bt_->getCurrentSongNumber(), n); setModifiedTrue(); }); /* Instrument list */ auto instToolBar = [&] { auto subMenu = [](const QList& actions) { auto menu = new QMenu(); menu->addActions(actions); auto btn = new QToolButton(); btn->setPopupMode(QToolButton::MenuButtonPopup); btn->setMenu(menu); btn->setDefaultAction(actions.front()); return btn; }; auto tb = new QToolBar(); tb->setIconSize(QSize(16, 16)); tb->addWidget(subMenu({ ui->actionNew_Instrument, ui->actionNew_Drumki_t })); tb->addActions({ ui->actionRemove_Instrument, ui->actionClone_Instrument }); tb->addSeparator(); tb->addWidget(subMenu({ ui->actionLoad_From_File, ui->actionImport_From_Bank_File })); tb->addWidget(subMenu({ ui->actionSave_To_File, ui->actionExport_To_Bank_File })); tb->addSeparator(); tb->addAction(ui->actionEdit); tb->addSeparator(); tb->addAction(ui->actionRename_Instrument); return tb; }(); ui->instrumentListGroupBox->layout()->addWidget(instToolBar); ui->instrumentList->installEventFilter(this); QObject::connect(ui->instrumentList, &DropDetectListWidget::itemDroppedAtItemIndex, this, &MainWindow::swapInstruments); /* Pattern editor */ ui->patternEditor->setCommandStack(comStack_); ui->patternEditor->installEventFilter(this); QObject::connect(ui->patternEditor, &PatternEditor::currentTrackChanged, ui->orderList, &OrderListEditor::onPatternEditorCurrentTrackChanged); QObject::connect(ui->patternEditor, &PatternEditor::currentOrderChanged, ui->orderList, &OrderListEditor::onPatternEditorCurrentOrderChanged); QObject::connect(ui->patternEditor, &PatternEditor::focusIn, this, &MainWindow::updateMenuByPattern); QObject::connect(ui->patternEditor, &PatternEditor::selected, this, &MainWindow::updateMenuByPatternSelection); QObject::connect(ui->patternEditor, &PatternEditor::instrumentEntered, this, [&](int num) { auto list = ui->instrumentList; if (num != -1) { for (int i = 0; i < list->count(); ++i) { if (list->item(i)->data(Qt::UserRole).toInt() == num) { list->setCurrentRow(i); return ; } } } }); QObject::connect(ui->patternEditor, &PatternEditor::volumeEntered, this, [&](int volume) { volume_->setValue(volume); }); QObject::connect(ui->patternEditor, &PatternEditor::effectEntered, this, [&](QString text) { ui->statusBar->showMessage(text, STATUS_DISPLAY_TIMEOUT); }); QObject::connect(ui->patternEditor, &PatternEditor::currentTrackChanged, this, &MainWindow::onCurrentTrackChanged); /* Order List */ ui->orderList->setCommandStack(comStack_); ui->orderList->installEventFilter(this); QObject::connect(ui->orderList, &OrderListEditor::currentTrackChanged, ui->patternEditor, &PatternEditor::onOrderListCurrentTrackChanged); QObject::connect(ui->orderList, &OrderListEditor::currentOrderChanged, ui->patternEditor, &PatternEditor::onOrderListCrrentOrderChanged); QObject::connect(ui->orderList, &OrderListEditor::orderEdited, ui->patternEditor, &PatternEditor::onOrderListEdited); QObject::connect(ui->orderList, &OrderListEditor::focusIn, this, &MainWindow::updateMenuByOrder); QObject::connect(ui->orderList, &OrderListEditor::selected, this, &MainWindow::updateMenuByOrderSelection); QObject::connect(ui->orderList, &OrderListEditor::currentTrackChanged, this, &MainWindow::onCurrentTrackChanged); /* Wave view */ visualTimer_.reset(new QTimer); QObject::connect(visualTimer_.get(), &QTimer::timeout, this, &MainWindow::updateVisuals); if (config.lock()->getVisibleWaveView()) visualTimer_->start(static_cast(std::round(1000. / config.lock()->getWaveViewFrameRate()))); /* Status bar */ statusStyle_ = new QLabel(ui->statusBar); statusStyle_->setAlignment(Qt::AlignTrailing | Qt::AlignVCenter); statusInst_ = new QLabel(ui->statusBar); statusInst_->setFixedWidth(110); statusInst_->setAlignment(Qt::AlignLeading | Qt::AlignVCenter); statusOctave_ = new QLabel(ui->statusBar); statusOctave_->setFixedWidth(90); statusOctave_->setAlignment(Qt::AlignLeading | Qt::AlignVCenter); statusIntr_ = new QLabel(ui->statusBar); statusIntr_->setFixedWidth(50); statusIntr_->setAlignment(Qt::AlignLeading | Qt::AlignVCenter); statusMixer_ = new QLabel(ui->statusBar); statusMixer_->setFixedWidth(200); statusMixer_->setAlignment(Qt::AlignLeading | Qt::AlignVCenter); statusBpm_ = new QLabel(ui->statusBar); statusBpm_->setFixedWidth(90); statusBpm_->setAlignment(Qt::AlignLeading | Qt::AlignVCenter); statusPlayPos_ = new QLabel(ui->statusBar); statusPlayPos_->setFixedWidth(40); statusPlayPos_->setAlignment(Qt::AlignLeading | Qt::AlignVCenter); ui->statusBar->addPermanentWidget(statusStyle_); ui->statusBar->addPermanentWidget(statusInst_); ui->statusBar->addPermanentWidget(statusOctave_); ui->statusBar->addPermanentWidget(statusIntr_); ui->statusBar->addPermanentWidget(statusMixer_); ui->statusBar->addPermanentWidget(statusBpm_); ui->statusBar->addPermanentWidget(statusPlayPos_); statusOctave_->setText(tr("Octave: %1").arg(bt_->getCurrentOctave())); statusIntr_->setText(QString::number(bt_->getModuleTickFrequency()) + QString("Hz")); ui->statusBar->showMessage(tr("Welcome to BambooTracker v%1!").arg(QString::fromStdString(Version::ofApplicationInString())), STATUS_DISPLAY_TIMEOUT); /* Bookmark */ bmManForm_ = std::make_unique(bt_, config_.lock()->getShowRowNumberInHex()); auto posJumpFunc = [&](int order, int step) { if (bt_->isPlaySong()) return; int song = bt_->getCurrentSongNumber(); if (static_cast(bt_->getOrderSize(song)) <= order) return; if (static_cast(bt_->getPatternSizeFromOrderNumber(song, order)) <= step) return; bt_->setCurrentOrderNumber(order); bt_->setCurrentStepNumber(step); ui->orderList->updatePositionByPositionJump(); ui->patternEditor->updatepositionByPositionJump(); activateWindow(); }; QObject::connect(bmManForm_.get(), &BookmarkManagerForm::positionJumpRequested, this, posJumpFunc); QObject::connect(bmManForm_.get(), &BookmarkManagerForm::modified, this, &MainWindow::setModifiedTrue); /* Key signature */ ksManForm_ = std::make_unique(bt_, config_.lock()->getShowRowNumberInHex()); QObject::connect(ksManForm_.get(), &KeySignatureManagerForm::positionJumpRequested, this, posJumpFunc); QObject::connect(ksManForm_.get(), &KeySignatureManagerForm::modified, this, [&] { ui->patternEditor->onPatternDataGlobalChanged(); setModifiedTrue(); }); /* Shortcuts */ { auto linkShortcut = [&](QAction* ptr) { ptr->setShortcutContext(Qt::WidgetWithChildrenShortcut); ui->orderList->addAction(ptr); ui->patternEditor->addAction(ptr); }; linkShortcut(&octUpSc_); QObject::connect(&octUpSc_, &QAction::triggered, this, [&] { changeOctave(true); }); linkShortcut(&octDownSc_); QObject::connect(&octDownSc_, &QAction::triggered, this, [&] { changeOctave(false); }); QObject::connect(&focusPtnSc_, &QShortcut::activated, this, [&] { ui->patternEditor->setFocus(); }); QObject::connect(&focusOdrSc_, &QShortcut::activated, this, [&] { ui->orderList->setFocus(); }); QObject::connect(&focusInstSc_, &QShortcut::activated, this, [&] { ui->instrumentList->setFocus(); updateMenuByInstrumentList(); }); auto playLinkShortcut = [&](QAction* ptr) { ptr->setShortcutContext(Qt::WidgetShortcut); ui->instrumentList->addAction(ptr); ui->orderList->addActionToPanel(ptr); ui->patternEditor->addActionToPanel(ptr); }; playLinkShortcut(ui->actionPlay); playLinkShortcut(&playAndStopSc_); QObject::connect(&playAndStopSc_, &QAction::triggered, this, [&] { if (bt_->isPlaySong()) stopPlaySong(); else startPlaySong(); }); playLinkShortcut(&playStepSc_); QObject::connect(&playStepSc_, &QAction::triggered, this, &MainWindow::playStep); playLinkShortcut(ui->actionPlay_From_Start); playLinkShortcut(ui->actionPlay_Pattern); playLinkShortcut(ui->actionPlay_From_Cursor); playLinkShortcut(ui->actionPlay_From_Marker); playLinkShortcut(ui->actionStop); instAddSc_ = std::make_unique(Qt::Key_Insert, ui->instrumentList, nullptr, nullptr, Qt::WidgetShortcut); QObject::connect(instAddSc_.get(), &QShortcut::activated, this, &MainWindow::addInstrument); linkShortcut(&goPrevOdrSc_); QObject::connect(&goPrevOdrSc_, &QAction::triggered, this, [&] { ui->orderList->onGoOrderRequested(false); }); linkShortcut(&goNextOdrSc_); QObject::connect(&goNextOdrSc_, &QAction::triggered, this, [&] { ui->orderList->onGoOrderRequested(true); }); linkShortcut(&prevInstSc_); QObject::connect(&prevInstSc_, &QAction::triggered, this, [&] { if (ui->instrumentList->count()) { int row = ui->instrumentList->currentRow(); if (row == -1) ui->instrumentList->setCurrentRow(0); else if (row > 0) ui->instrumentList->setCurrentRow(row - 1); } }); linkShortcut(&nextInstSc_); QObject::connect(&nextInstSc_, &QAction::triggered, this, [&] { int cnt = ui->instrumentList->count(); if (cnt) { int row = ui->instrumentList->currentRow(); if (row == -1) ui->instrumentList->setCurrentRow(cnt - 1); else if (row < cnt - 1) ui->instrumentList->setCurrentRow(row + 1); } }); linkShortcut(&incPtnSizeSc_); QObject::connect(&incPtnSizeSc_, &QAction::triggered, this, [&] { ui->patternSizeSpinBox->setValue(ui->patternSizeSpinBox->value() + 1); }); linkShortcut(&decPtnSizeSc_); QObject::connect(&decPtnSizeSc_, &QAction::triggered, this, [&] { ui->patternSizeSpinBox->setValue(ui->patternSizeSpinBox->value() - 1); }); linkShortcut(&incEditStepSc_); QObject::connect(&incEditStepSc_, &QAction::triggered, this, [&] { ui->editableStepSpinBox->setValue(ui->editableStepSpinBox->value() + 1); }); linkShortcut(&decEditStepSc_); QObject::connect(&decEditStepSc_, &QAction::triggered, this, [&] { ui->editableStepSpinBox->setValue(ui->editableStepSpinBox->value() - 1); }); linkShortcut(&prevSongSc_); QObject::connect(&prevSongSc_, &QAction::triggered, this, [&] { if (ui->songComboBox->isEnabled()) { ui->songComboBox->setCurrentIndex(std::max(ui->songComboBox->currentIndex() - 1, 0)); } }); linkShortcut(&nextSongSc_); QObject::connect(&nextSongSc_, &QAction::triggered, this, [&] { if (ui->songComboBox->isEnabled()) { ui->songComboBox->setCurrentIndex(std::min(ui->songComboBox->currentIndex() + 1, ui->songComboBox->count() - 1)); } }); linkShortcut(&jamVolUpSc_); QObject::connect(&jamVolUpSc_, &QAction::triggered, this, [&] { volume_->setValue(volume_->value() + 1); }); linkShortcut(&jamVolDownSc_); QObject::connect(&jamVolDownSc_, &QAction::triggered, this, [&] { volume_->setValue(volume_->value() - 1); }); setShortcuts(); } /* Clipboard */ QObject::connect(QApplication::clipboard(), &QClipboard::dataChanged, this, [&]() { if (isEditedOrder_) updateMenuByOrder(); else if (isEditedPattern_) updateMenuByPattern(); }); /* Welcome dialog */ { const QString text = "

" + tr("Welcome to BambooTracker!") + "

" + "
" "" "
" "

" + tr("Don't know where to start?") + "

" + "

" + tr("Check the demo modules and instruments included " "with your download of BambooTracker.") + "

" + "

" + tr("Need a list of effects and shortcuts?") + "

" + "

" + tr("Check the Help menu at the top of the window.") + "

" + "

" + tr("Still lost?") + "

" + "

" + tr("The README.md has a link to our Discord server.") + "

" + "

" + tr("Think you've found a bug? Missing a feature?") + "

" + "

" + tr("BambooTracker is still in development, bugs and missing " "features are to be expected. So we need your help!") + "

    " + //: %1 is the link to the issue submission page in GitHub. "
  • " + tr("Please report any bugs you find and requests and features " "you'd like to see on our Discord server or our bug tracker (%1).") .arg("" "https://github.com/BambooTracker/BambooTracker/issues/new/choose") + "
  • " + "
  • " + tr("If you're a developer yourself or would like to start being one, " "consider contributing to the project yourself. " "Any help would be appreciated!") + "
  • " + "

"; welcomeDialog_ = std::make_unique(QMessageBox::NoIcon, tr("Welcome"), text, QMessageBox::Ok, this); welcomeDialog_->setWindowModality(Qt::ApplicationModal); welcomeDialog_->setWindowFlags(welcomeDialog_->windowFlags() & ~Qt::WindowContextHelpButtonHint); welcomeDialog_->setTextFormat(Qt::RichText); if (isFirstLaunch) on_action_Welcome_triggered(); } /* MIDI */ setMidiConfiguration(); midiKeyEventMethod_ = metaObject()->indexOfSlot("midiKeyEvent(uchar,uchar,uchar)"); Q_ASSERT(midiKeyEventMethod_ != -1); midiProgramEventMethod_ = metaObject()->indexOfSlot("midiProgramEvent(uchar,uchar)"); Q_ASSERT(midiProgramEventMethod_ != -1); MidiInterface::getInstance().installInputHandler(&midiThreadReceivedEvent, this); /* Audio stream */ stream_ = std::make_shared(); stream_->setTickUpdateCallback(+[](void* cbPtr) -> int { auto bt = reinterpret_cast(cbPtr); return bt->streamCountUp(); }, bt_.get()); stream_->setGenerateCallback(+[](int16_t* container, size_t nSamples, void* cbPtr) { auto bt = reinterpret_cast(cbPtr); return bt->getStreamSamples(container, nSamples); }, bt_.get()); QObject::connect(stream_.get(), &AudioStream::streamInterrupted, this, &MainWindow::onNewTickSignaled); QObject::connect(stream_.get(), &AudioStream::streamErrorInCallback, this, [&](const QVariant&) { QMessageBox::critical(this, tr("Error"), tr("An error occurred in the audio playback.\n" "Please change the settings in the configuration.")); }); QString audioApi = gui_utils::utf8ToQString(config.lock()->getSoundAPI()); if (isFirstLaunch) { try { bool streamState = false; QString streamErr; for (const QString& audioApi : stream_->getAvailableBackends()) { config.lock()->setSoundAPI(audioApi.toUtf8().toStdString()); QString audioDevice = stream_->getDefaultOutputDevice(audioApi); config.lock()->setSoundDevice(audioDevice.toUtf8().toStdString()); streamState = stream_->initialize( static_cast(bt_->getStreamRate()), static_cast(bt_->getStreamDuration()), bt_->getModuleTickFrequency(), audioApi, audioDevice, &streamErr); if (streamState) break; } if (streamState) { uint32_t sr = stream_->getStreamRate(); if (config.lock()->getSampleRate() != sr) { showStreamRateWarningDialog(sr); bt_->setStreamRate(sr); } } else { showStreamFailedDialog(streamErr); } } catch (std::exception& e) { showStreamFailedDialog(e.what()); } } else { // Ordinary launch bool savedApiExists = false; const std::vector audioApis = stream_->getAvailableBackends(); for (const QString& api : audioApis) { if (api.toUtf8().toStdString() == config.lock()->getSoundAPI()) { savedApiExists = true; break; } } if (!savedApiExists) { audioApi = audioApis.front(); config.lock()->setSoundAPI(audioApi.toUtf8().toStdString()); } QString audioDevice = gui_utils::utf8ToQString(config.lock()->getSoundDevice()); bool savedDeviceExists = false; for (const QString& device : stream_->getAvailableDevices(audioApi)) { if (device.toUtf8().toStdString() == config.lock()->getSoundDevice()) { savedDeviceExists = true; break; } } if (!savedDeviceExists) { audioDevice = stream_->getDefaultOutputDevice(audioApi); config.lock()->setSoundDevice(audioDevice.toUtf8().toStdString()); } try { QString streamErr; bool streamState = stream_->initialize( static_cast(bt_->getStreamRate()), static_cast(bt_->getStreamDuration()), bt_->getModuleTickFrequency(), audioApi, audioDevice, &streamErr); if (streamState) { uint32_t sr = stream_->getStreamRate(); if (config.lock()->getSampleRate() != sr) { showStreamRateWarningDialog(sr); bt_->setStreamRate(sr); } } else { showStreamFailedDialog(streamErr); } } catch (std::exception& e) { showStreamFailedDialog(e.what()); } } RealChipInterfaceType intf = config.lock()->getRealChipInterface(); if (intf != RealChipInterfaceType::NONE) { tickTimerForRealChip_ = std::make_unique(); tickTimerForRealChip_->setInterval(1000000 / bt_->getModuleTickFrequency()); tickEventMethod_ = metaObject()->indexOfSlot("onNewTickSignaledRealChip()"); Q_ASSERT(tickEventMethod_ != -1); tickTimerForRealChip_->setFunction([&]{ QMetaMethod method = this->metaObject()->method(this->tickEventMethod_); method.invoke(this, Qt::QueuedConnection); }); setRealChipInterface(intf); tickTimerForRealChip_->start(); } /* Load module */ if (filePath.isEmpty()) { loadModule(); setInitialSelectedInstrument(); assignADPCMSamples(); if (!tickTimerForRealChip_) { try { stream_->start(); } catch (std::exception& e) { showStreamFailedDialog(e.what()); } } } else { openModule(QFileInfo(filePath).absoluteFilePath()); // If use emulation, stream starts } } MainWindow::~MainWindow() { MidiInterface::getInstance().uninstallInputHandler(&midiThreadReceivedEvent, this); stream_->shutdown(); } void MainWindow::onApplicationStateChanged(Qt::ApplicationState state) { switch (state) { case Qt::ApplicationHidden: case Qt::ApplicationInactive: if (bt_) bt_->jamkeyOffAll(); break; default: break; } } bool MainWindow::eventFilter(QObject* watched, QEvent* event) { if (auto fmForm = qobject_cast(watched)) { // Change current instrument by activating FM editor if (event->type() == QEvent::WindowActivate) { int row = findRowFromInstrumentList(fmForm->getInstrumentNumber()); ui->instrumentList->setCurrentRow(row); } else if (event->type() == QEvent::Resize) { config_.lock()->setInstrumentFMWindowWidth(fmForm->width()); config_.lock()->setInstrumentFMWindowHeight(fmForm->height()); } return false; } if (auto ssgForm = qobject_cast(watched)) { // Change current instrument by activating SSG editor if (event->type() == QEvent::WindowActivate) { int row = findRowFromInstrumentList(ssgForm->getInstrumentNumber()); ui->instrumentList->setCurrentRow(row); } else if (event->type() == QEvent::Resize) { config_.lock()->setInstrumentSSGWindowWidth(ssgForm->width()); config_.lock()->setInstrumentSSGWindowHeight(ssgForm->height()); } return false; } if (auto adpcmForm = qobject_cast(watched)) { // Change current instrument by activating ADPCM editor if (event->type() == QEvent::WindowActivate) { int row = findRowFromInstrumentList(adpcmForm->getInstrumentNumber()); ui->instrumentList->setCurrentRow(row); } else if (event->type() == QEvent::Resize) { config_.lock()->setInstrumentADPCMWindowWidth(adpcmForm->width()); config_.lock()->setInstrumentADPCMWindowHeight(adpcmForm->height()); } return false; } if (auto kitForm = qobject_cast(watched)) { // Change current instrument by activating drumkit editor if (event->type() == QEvent::WindowActivate) { int row = findRowFromInstrumentList(kitForm->getInstrumentNumber()); ui->instrumentList->setCurrentRow(row); } else if (event->type() == QEvent::Resize) { config_.lock()->setInstrumentDrumkitWindowWidth(kitForm->width()); config_.lock()->setInstrumentDrumkitWindowHeight(kitForm->height()); } return false; } if (watched == ui->instrumentList) { if (event->type() == QEvent::FocusIn) updateMenuByInstrumentList(); return false; } if (watched == renamingInstEdit_) { if (event->type() == QEvent::FocusOut) finishRenamingInstrument(); return false; } return false; } void MainWindow::showEvent(QShowEvent*) { if (!hasShownOnce_) { int y = config_.lock()->getMainWindowVerticalSplit(); if (y == -1) { config_.lock()->setMainWindowVerticalSplit(ui->splitter->sizes().at(0)); } else { ui->splitter->setSizes({ y, ui->splitter->height() - ui->splitter->handleWidth() - y }); } hasShownOnce_ = true; } } void MainWindow::keyPressEvent(QKeyEvent *event) { if (!event->isAutoRepeat()) { // Musical keyboard Qt::Key qtKey = static_cast(event->key()); try { bt_->jamKeyOn(getJamKeyFromLayoutMapping(qtKey, config_), !config_.lock()->getFixJammingVolume()); } catch (std::invalid_argument&) {} } } void MainWindow::keyReleaseEvent(QKeyEvent *event) { int key = event->key(); if (!event->isAutoRepeat()) { // Musical keyboard Qt::Key qtKey = static_cast(key); try { bt_->jamKeyOff(getJamKeyFromLayoutMapping(qtKey, config_)); } catch (std::invalid_argument&) {} } } void MainWindow::dragEnterEvent(QDragEnterEvent* event) { auto mime = event->mimeData(); if (mime->hasUrls()) { const auto urls = mime->urls(); for (auto& url : urls) { const std::string ext = QFileInfo(url.toLocalFile()).suffix().toLower().toStdString(); if (io::ModuleIO::getInstance().testLoadableFormat(ext) || io::BankIO::getInstance().testLoadableFormat(ext)) { if (urls.size() == 1) event->acceptProposedAction(); } if (io::InstrumentIO::getInstance().testLoadableFormat(ext)) { continue; } return; } if (!urls.empty()) event->acceptProposedAction(); // For instruments } } void MainWindow::dropEvent(QDropEvent* event) { const auto urls = event->mimeData()->urls(); for (const auto& url : urls) { const QString file = url.toLocalFile(); const std::string ext = QFileInfo(file).suffix().toLower().toStdString(); if (io::ModuleIO::getInstance().testLoadableFormat(ext)) { if (isWindowModified()) { switch (ModuleSaveCheckDialog(bt_->getModuleTitle(), this).exec()) { case QMessageBox::Yes: if (!on_actionSave_triggered()) return; break; case QMessageBox::No: break; case QMessageBox::Cancel: return; default: break; } } bt_->stopPlaySong(); lockWidgets(false); openModule(file); return; } else if (io::InstrumentIO::getInstance().testLoadableFormat(ext)) { funcLoadInstrument(file); } if (io::BankIO::getInstance().testLoadableFormat(ext)) { funcImportInstrumentsFromBank(file); return; } } } void MainWindow::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); if (!isMaximized()) { // Check previous size config_.lock()->setMainWindowWidth(event->oldSize().width()); config_.lock()->setMainWindowHeight(event->oldSize().height()); } } void MainWindow::moveEvent(QMoveEvent* event) { QWidget::moveEvent(event); if (!isMaximized()) { // Check previous position config_.lock()->setMainWindowX(event->oldPos().x()); config_.lock()->setMainWindowY(event->oldPos().y()); } } void MainWindow::closeEvent(QCloseEvent *event) { if (isWindowModified()) { switch (ModuleSaveCheckDialog(bt_->getModuleTitle(), this).exec()) { case QMessageBox::Yes: if (!on_actionSave_triggered()) { event->ignore(); return; } break; case QMessageBox::No: break; case QMessageBox::Cancel: event->ignore(); return; default: break; } } if (isMaximized()) { config_.lock()->setMainWindowMaximized(true); } else { config_.lock()->setMainWindowMaximized(false); config_.lock()->setMainWindowWidth(width()); config_.lock()->setMainWindowHeight(height()); config_.lock()->setMainWindowX(x()); config_.lock()->setMainWindowY(y()); } config_.lock()->setMainWindowVerticalSplit(ui->splitter->sizes().at(0)); auto& mainTbConfig = config_.lock()->getMainToolbarConfiguration(); auto& subTbConfig = config_.lock()->getSubToolbarConfiguration(); auto mainTbArea = toolBarArea(ui->mainToolBar); auto subTbArea = toolBarArea(ui->subToolBar); if (ui->mainToolBar->isFloating()) { mainTbConfig.setPosition(Configuration::ToolbarPosition::FloatPorition); mainTbConfig.setX(ui->mainToolBar->x()); mainTbConfig.setY(ui->mainToolBar->y()); } else { mainTbConfig.setPosition(utils::findMapValue(TB_POS_, mainTbArea)->first); mainTbConfig.setNumber(0); mainTbConfig.setBreakBefore(false); } if (ui->subToolBar->isFloating()) { subTbConfig.setPosition(Configuration::ToolbarPosition::FloatPorition); subTbConfig.setX(ui->subToolBar->x()); subTbConfig.setY(ui->subToolBar->y()); } else { subTbConfig.setPosition(utils::findMapValue(TB_POS_, mainTbArea)->first); subTbConfig.setNumber(0); subTbConfig.setBreakBefore(false); } if (mainTbArea == subTbArea) { auto mainPos = ui->mainToolBar->pos(); auto subPos = ui->subToolBar->pos(); switch (mainTbArea) { case Qt::TopToolBarArea: { if (mainPos.x() == subPos.x()) { bool cond = (mainPos.y() < subPos.y()); mainTbConfig.setNumber(cond ? 0 : 1); mainTbConfig.setBreakBefore(!cond); subTbConfig.setNumber(cond ? 1 : 0); subTbConfig.setBreakBefore(cond); } else { bool cond = (mainPos.x() < subPos.x()); mainTbConfig.setNumber(cond ? 0 : 1); mainTbConfig.setBreakBefore(false); subTbConfig.setNumber(cond ? 1 : 0); subTbConfig.setBreakBefore(false); } break; } case Qt::BottomToolBarArea: { if (mainPos.x() == subPos.x()) { bool cond = (subPos.y() < mainPos.y()); mainTbConfig.setNumber(cond ? 0 : 1); mainTbConfig.setBreakBefore(!cond); subTbConfig.setNumber(cond ? 1 : 0); subTbConfig.setBreakBefore(cond); } else { bool cond = (mainPos.x() < subPos.x()); mainTbConfig.setNumber(cond ? 0 : 1); mainTbConfig.setBreakBefore(false); subTbConfig.setNumber(cond ? 1 : 0); subTbConfig.setBreakBefore(false); } break; } case Qt::LeftToolBarArea: { if (mainPos.x() == subPos.x()) { bool cond = (mainPos.y() < subPos.y()); mainTbConfig.setNumber(cond ? 0 : 1); mainTbConfig.setBreakBefore(false); subTbConfig.setNumber(cond ? 1 : 0); subTbConfig.setBreakBefore(false); } else { bool cond = (mainPos.x() < subPos.x()); mainTbConfig.setNumber(cond ? 0 : 1); mainTbConfig.setBreakBefore(!cond); subTbConfig.setNumber(cond ? 1 : 0); subTbConfig.setBreakBefore(cond); } break; } case Qt::RightToolBarArea: { if (mainPos.x() == subPos.x()) { bool cond = (mainPos.y() < subPos.y()); mainTbConfig.setNumber(cond ? 0 : 1); mainTbConfig.setBreakBefore(false); subTbConfig.setNumber(cond ? 1 : 0); subTbConfig.setBreakBefore(false); } else { bool cond = (subPos.x() < mainPos.x()); mainTbConfig.setNumber(cond ? 0 : 1); mainTbConfig.setBreakBefore(!cond); subTbConfig.setNumber(cond ? 1 : 0); subTbConfig.setBreakBefore(cond); } break; } default: break; } } config_.lock()->setVisibleToolbar(ui->mainToolBar->isVisible()); config_.lock()->setVisibleStatusBar(ui->statusBar->isVisible()); config_.lock()->setFollowMode(bt_->isFollowPlay()); instDialogMan_->removeAll(); io::saveFileHistory(fileHistory_); bmManForm_->close(); ksManForm_->close(); event->accept(); } void MainWindow::freezeViews() { ui->orderList->freeze(); ui->patternEditor->freeze(); } void MainWindow::unfreezeViews() { ui->orderList->unfreeze(); ui->patternEditor->unfreeze(); } void MainWindow::setShortcuts() { auto shortcuts = config_.lock()->getShortcuts(); octUpSc_.setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::OctaveUp))); octDownSc_.setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::OctaveDown))); focusPtnSc_.setKey(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::FocusOnPattern))); focusOdrSc_.setKey(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::FocusOnOrder))); focusInstSc_.setKey(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::FocusOnInstrument))); playAndStopSc_.setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::PlayAndStop))); ui->actionPlay->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::Play))); ui->actionPlay_From_Start->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::PlayFromStart))); ui->actionPlay_Pattern->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::PlayPattern))); ui->actionPlay_From_Cursor->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::PlayFromCursor))); ui->actionPlay_From_Marker->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::PlayFromMarker))); playStepSc_.setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::PlayStep))); ui->actionStop->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::Stop))); ui->actionEdit_Mode->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::ToggleEditJam))); ui->actionSet_Ro_w_Marker->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::SetMarker))); ui->actionMix->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::PasteMix))); ui->actionOverwrite->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::PasteOverwrite))); ui->action_Insert->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::PasteInsert))); ui->actionAll->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::SelectAll))); ui->actionNone->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::Deselect))); ui->actionRow->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::SelectRow))); ui->actionColumn->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::SelectColumn))); ui->actionPattern->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::SelectColumn))); ui->actionOrder->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::SelectOrder))); ui->action_Go_To->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::GoToStep))); ui->actionToggle_Track->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::ToggleTrack))); ui->actionSolo_Track->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::SoloTrack))); ui->actionInterpolate->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::Interpolate))); goPrevOdrSc_.setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::GoToPrevOrder))); goNextOdrSc_.setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::GoToNextOrder))); ui->action_Toggle_Bookmark->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::ToggleBookmark))); ui->action_Previous_Bookmark->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::PrevBookmark))); ui->action_Next_Bookmark->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::NextBookmark))); ui->actionDecrease_Note->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::DecreaseNote))); ui->actionIncrease_Note->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::IncreaseNote))); ui->actionDecrease_Octave->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::DecreaseOctave))); ui->actionIncrease_Octave->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::IncreaseOctave))); prevInstSc_.setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::PrevInstrument))); nextInstSc_.setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::NextInstrument))); ui->action_Instrument_Mask->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::MaskInstrument))); ui->action_Volume_Mask->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::MaskVolume))); ui->actionEdit->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::EditInstrument))); ui->actionFollow_Mode->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::FollowMode))); ui->actionDuplicate_Order->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::DuplicateOrder))); ui->actionClone_Patterns->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::ClonePatterns))); ui->actionClone_Order->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::CloneOrder))); ui->actionReplace_Instrument->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::ReplaceInstrument))); ui->actionExpand->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::ExpandPattern))); ui->actionShrink->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::ShrinkPattern))); ui->actionFine_Decrease_Values->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::FineDecreaseValues))); ui->actionFine_Increase_Values->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::FineIncreaseValues))); ui->actionCoarse_D_ecrease_Values->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::CoarseDecreaseValues))); ui->actionCoarse_I_ncrease_Values->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::CoarseIncreaseValuse))); incPtnSizeSc_.setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::IncreasePatternSize))); decPtnSizeSc_.setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::DecreasePatternSize))); incEditStepSc_.setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::IncreaseEditStep))); decEditStepSc_.setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::DecreaseEditStep))); ui->action_Effect_List->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::DisplayEffectList))); prevSongSc_.setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::PreviousSong))); nextSongSc_.setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::NextSong))); jamVolUpSc_.setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::JamVolumeUp))); jamVolDownSc_.setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::JamVolumeDown))); ui->orderList->onShortcutUpdated(); ui->patternEditor->onShortcutUpdated(); } void MainWindow::updateInstrumentListColors() { ui->instrumentList->setStyleSheet( QString("QListWidget { color: %1; background: %2; }") .arg(palette_->ilistTextColor.name(QColor::HexArgb), palette_->ilistBackColor.name(QColor::HexArgb)) + QString("QListWidget::item:hover { color: %1; background: %2; }") .arg(palette_->ilistTextColor.name(QColor::HexArgb), palette_->ilistHovBackColor.name(QColor::HexArgb)) + QString("QListWidget::item:selected { color: %1; background: %2; }") .arg(palette_->ilistTextColor.name(QColor::HexArgb), palette_->ilistSelBackColor.name(QColor::HexArgb)) + QString("QListWidget::item:selected:hover { color: %1; background: %2; }") .arg(palette_->ilistTextColor.name(QColor::HexArgb), palette_->ilistHovSelBackColor.name(QColor::HexArgb))); } void MainWindow::setOrderListGroupMaximumWidth() { ui->orderListGroupBox->setMaximumWidth( ui->orderListGroupBox->contentsMargins().left() + ui->orderListGroupBox->layout()->contentsMargins().left() + ui->orderList->maximumWidth() + ui->orderListGroupBox->layout()->contentsMargins().right() + ui->orderListGroupBox->contentsMargins().right()); } /********** MIDI **********/ void MainWindow::midiThreadReceivedEvent(double delay, const uint8_t *msg, size_t len, void *userData) { MainWindow *self = reinterpret_cast(userData); Q_UNUSED(delay) // Note-On/Note-Off if (len == 3 && (msg[0] & 0xe0) == 0x80) { uint8_t status = msg[0]; uint8_t key = msg[1]; uint8_t velocity = msg[2]; QMetaMethod method = self->metaObject()->method(self->midiKeyEventMethod_); method.invoke(self, Qt::QueuedConnection, Q_ARG(uchar, status), Q_ARG(uchar, key), Q_ARG(uchar, velocity)); } // Program change else if (len == 2 && (msg[0] & 0xf0) == 0xc0) { uint8_t status = msg[0]; uint8_t program = msg[1]; QMetaMethod method = self->metaObject()->method(self->midiProgramEventMethod_); method.invoke(self, Qt::QueuedConnection, Q_ARG(uchar, status), Q_ARG(uchar, program)); } } void MainWindow::midiKeyEvent(uchar status, uchar key, uchar velocity) { bool release = ((status & 0xf0) == 0x80) || velocity == 0; int k = static_cast(key) - 12; octave_->setValue(k / 12); if (importBankDialog_) { if (bankJamMidiCtrl_.load()) return; importBankDialog_->onJamKeyOffByMidi(k); if (!release) importBankDialog_->onJamKeyOnByMidi(k); return; } int n = instDialogMan_->getActivatedEditorIndex(); if (n == -1) { bt_->jamKeyOff(k); // possibility to recover on stuck note if (!release) bt_->jamKeyOn(k, !config_.lock()->getFixJammingVolume()); } else { SoundSource src = bt_->getInstrument(n)->getSoundSource(); bt_->jamKeyOffForced(k, src); // possibility to recover on stuck note if (!release) bt_->jamKeyOnForced(k, src, !config_.lock()->getFixJammingVolume()); } } void MainWindow::midiProgramEvent(uchar status, uchar program) { Q_UNUSED(status) int row = findRowFromInstrumentList(program); ui->instrumentList->setCurrentRow(row); } /********** Instrument list **********/ void MainWindow::addInstrument() { SoundSource src = bt_->getCurrentTrackAttribute().source; switch (src) { case SoundSource::FM: case SoundSource::SSG: case SoundSource::ADPCM: { std::unordered_map map = { { SoundSource::FM, InstrumentType::FM }, { SoundSource::SSG, InstrumentType::SSG }, { SoundSource::ADPCM, InstrumentType::ADPCM }, }; auto& list = ui->instrumentList; int num = bt_->findFirstFreeInstrumentNumber(); if (num == -1) return; // Maximum count check QString name = tr("Instrument %1").arg(num); bt_->addInstrument(num, map.at(src), name.toUtf8().toStdString()); comStack_->push(new AddInstrumentQtCommand( list, num, name, map.at(src), instDialogMan_, this, config_.lock()->getWriteOnlyUsedSamples())); ui->instrumentList->setCurrentRow(num); break; } case SoundSource::RHYTHM: break; } } void MainWindow::addDrumkit() { auto& list = ui->instrumentList; int num = bt_->findFirstFreeInstrumentNumber(); if (num == -1) return; // Maximum count check QString name = tr("Instrument %1").arg(num); bt_->addInstrument(num, InstrumentType::Drumkit, name.toUtf8().toStdString()); comStack_->push(new AddInstrumentQtCommand( list, num, name, InstrumentType::Drumkit, instDialogMan_, this, config_.lock()->getWriteOnlyUsedSamples())); ui->instrumentList->setCurrentRow(num); } void MainWindow::removeInstrument(int row) { if (row < 0) return; auto& list = ui->instrumentList; int num = list->item(row)->data(Qt::UserRole).toInt(); auto inst = bt_->getInstrument(num); bool updateRequest = false; if (config_.lock()->getWriteOnlyUsedSamples()){ if (inst->getSoundSource() == SoundSource::ADPCM) { size_t size = bt_->getSampleADPCMUsers(dynamic_cast( inst.get())->getSampleNumber()).size(); if (size == 1) updateRequest = true; } } bt_->removeInstrument(num); // Cancel renaming instrument if (list->item(row) == renamingInstItem_) { list->removeItemWidget(renamingInstItem_); renamingInstItem_ = nullptr; renamingInstEdit_ = nullptr; } comStack_->push(new RemoveInstrumentQtCommand(list, num, row, gui_utils::utf8ToQString(inst->getName()), inst->getType(), instDialogMan_, this, updateRequest)); } void MainWindow::openInstrumentEditor() { auto item = ui->instrumentList->currentItem(); int num = item->data(Qt::UserRole).toInt(); if (!instDialogMan_->hasShownEditor(num)) { // Create editor InstrumentEditor* editor; auto inst = bt_->getInstrument(num); switch (inst->getType()) { case InstrumentType::FM: { auto fmEditor = new FmInstrumentEditor(num, this); editor = fmEditor; fmEditor->setCore(bt_); fmEditor->setConfiguration(config_.lock()); fmEditor->setColorPalette(palette_); fmEditor->resize(config_.lock()->getInstrumentFMWindowWidth(), config_.lock()->getInstrumentFMWindowHeight()); QObject::connect(fmEditor, &FmInstrumentEditor::envelopeNumberChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentFMEnvelopeNumberChanged); QObject::connect(fmEditor, &FmInstrumentEditor::envelopeParameterChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentFMEnvelopeParameterChanged); QObject::connect(fmEditor, &FmInstrumentEditor::lfoNumberChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentFMLFONumberChanged); QObject::connect(fmEditor, &FmInstrumentEditor::lfoParameterChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentFMLFOParameterChanged); QObject::connect(fmEditor, &FmInstrumentEditor::operatorSequenceNumberChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentFMOperatorSequenceNumberChanged); QObject::connect(fmEditor, &FmInstrumentEditor::operatorSequenceParameterChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentFMOperatorSequenceParameterChanged); QObject::connect(fmEditor, &FmInstrumentEditor::arpeggioNumberChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentFMArpeggioNumberChanged); QObject::connect(fmEditor, &FmInstrumentEditor::arpeggioParameterChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentFMArpeggioParameterChanged); QObject::connect(fmEditor, &FmInstrumentEditor::pitchNumberChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentFMPitchNumberChanged); QObject::connect(fmEditor, &FmInstrumentEditor::pitchParameterChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentFMPitchParameterChanged); QObject::connect(fmEditor, &FmInstrumentEditor::jamKeyOnEvent, this, [&](JamKey key) { bt_->jamKeyOnForced(key, SoundSource::FM, !config_.lock()->getFixJammingVolume()); }, Qt::DirectConnection); QObject::connect(fmEditor, &FmInstrumentEditor::jamKeyOffEvent, this, [&](JamKey key) { bt_->jamKeyOffForced(key, SoundSource::FM); }, Qt::DirectConnection); QObject::connect(fmEditor, &FmInstrumentEditor::modified, this, &MainWindow::setModifiedTrue); fmEditor->installEventFilter(this); instDialogMan_->onInstrumentFMEnvelopeNumberChanged(); instDialogMan_->onInstrumentFMLFONumberChanged(); instDialogMan_->onInstrumentFMOperatorSequenceNumberChanged(); instDialogMan_->onInstrumentFMArpeggioNumberChanged(); instDialogMan_->onInstrumentFMPitchNumberChanged(); break; } case InstrumentType::SSG: { auto ssgEditor = new SsgInstrumentEditor(num, this); editor = ssgEditor; ssgEditor->setCore(bt_); ssgEditor->setConfiguration(config_.lock()); ssgEditor->setColorPalette(palette_); ssgEditor->resize(config_.lock()->getInstrumentSSGWindowWidth(), config_.lock()->getInstrumentSSGWindowHeight()); QObject::connect(ssgEditor, &SsgInstrumentEditor::waveformNumberChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentSSGWaveformNumberChanged); QObject::connect(ssgEditor, &SsgInstrumentEditor::waveformParameterChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentSSGWaveformParameterChanged); QObject::connect(ssgEditor, &SsgInstrumentEditor::toneNoiseNumberChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentSSGToneNoiseNumberChanged); QObject::connect(ssgEditor, &SsgInstrumentEditor::toneNoiseParameterChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentSSGToneNoiseParameterChanged); QObject::connect(ssgEditor, &SsgInstrumentEditor::envelopeNumberChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentSSGEnvelopeNumberChanged); QObject::connect(ssgEditor, &SsgInstrumentEditor::envelopeParameterChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentSSGEnvelopeParameterChanged); QObject::connect(ssgEditor, &SsgInstrumentEditor::arpeggioNumberChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentSSGArpeggioNumberChanged); QObject::connect(ssgEditor, &SsgInstrumentEditor::arpeggioParameterChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentSSGArpeggioParameterChanged); QObject::connect(ssgEditor, &SsgInstrumentEditor::pitchNumberChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentSSGPitchNumberChanged); QObject::connect(ssgEditor, &SsgInstrumentEditor::pitchParameterChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentSSGPitchParameterChanged); QObject::connect(ssgEditor, &SsgInstrumentEditor::jamKeyOnEvent, this, [&](JamKey key) { bt_->jamKeyOnForced(key, SoundSource::SSG, !config_.lock()->getFixJammingVolume()); }, Qt::DirectConnection); QObject::connect(ssgEditor, &SsgInstrumentEditor::jamKeyOffEvent, this, [&](JamKey key) { bt_->jamKeyOffForced(key, SoundSource::SSG); } , Qt::DirectConnection); QObject::connect(ssgEditor, &SsgInstrumentEditor::modified, this, &MainWindow::setModifiedTrue); ssgEditor->installEventFilter(this); instDialogMan_->onInstrumentSSGWaveformNumberChanged(); instDialogMan_->onInstrumentSSGToneNoiseNumberChanged(); instDialogMan_->onInstrumentSSGEnvelopeNumberChanged(); instDialogMan_->onInstrumentSSGArpeggioNumberChanged(); instDialogMan_->onInstrumentSSGPitchNumberChanged(); break; } case InstrumentType::ADPCM: { auto adpcmEditor = new AdpcmInstrumentEditor(num, this); editor = adpcmEditor; adpcmEditor->setCore(bt_); adpcmEditor->setConfiguration(config_.lock()); adpcmEditor->setColorPalette(palette_); adpcmEditor->resize(config_.lock()->getInstrumentADPCMWindowWidth(), config_.lock()->getInstrumentADPCMWindowHeight()); QObject::connect(adpcmEditor, &AdpcmInstrumentEditor::sampleNumberChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentADPCMSampleNumberChanged); QObject::connect(adpcmEditor, &AdpcmInstrumentEditor::sampleParameterChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentADPCMSampleParameterChanged); QObject::connect(adpcmEditor, &AdpcmInstrumentEditor::sampleAssignRequested, this, &MainWindow::assignADPCMSamples); QObject::connect(adpcmEditor, &AdpcmInstrumentEditor::sampleMemoryChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentADPCMSampleMemoryUpdated); QObject::connect(adpcmEditor, &AdpcmInstrumentEditor::envelopeNumberChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentADPCMEnvelopeNumberChanged); QObject::connect(adpcmEditor, &AdpcmInstrumentEditor::envelopeParameterChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentADPCMEnvelopeParameterChanged); QObject::connect(adpcmEditor, &AdpcmInstrumentEditor::arpeggioNumberChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentADPCMArpeggioNumberChanged); QObject::connect(adpcmEditor, &AdpcmInstrumentEditor::arpeggioParameterChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentADPCMArpeggioParameterChanged); QObject::connect(adpcmEditor, &AdpcmInstrumentEditor::pitchNumberChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentADPCMPitchNumberChanged); QObject::connect(adpcmEditor, &AdpcmInstrumentEditor::pitchParameterChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentADPCMPitchParameterChanged); QObject::connect(adpcmEditor, &AdpcmInstrumentEditor::jamKeyOnEvent, this, [&](JamKey key) { bt_->jamKeyOnForced(key, SoundSource::ADPCM, !config_.lock()->getFixJammingVolume()); }, Qt::DirectConnection); QObject::connect(adpcmEditor, &AdpcmInstrumentEditor::jamKeyOffEvent, this, [&](JamKey key) { bt_->jamKeyOffForced(key, SoundSource::ADPCM); }, Qt::DirectConnection); QObject::connect(adpcmEditor, &AdpcmInstrumentEditor::modified, this, &MainWindow::setModifiedTrue); adpcmEditor->installEventFilter(this); instDialogMan_->onInstrumentADPCMSampleNumberChanged(); instDialogMan_->onInstrumentADPCMEnvelopeNumberChanged(); instDialogMan_->onInstrumentADPCMArpeggioNumberChanged(); instDialogMan_->onInstrumentADPCMPitchNumberChanged(); break; } case InstrumentType::Drumkit: { auto kitEditor = new AdpcmDrumkitEditor(num, this); editor = kitEditor; kitEditor->setCore(bt_); kitEditor->setConfiguration(config_.lock()); kitEditor->setColorPalette(palette_); kitEditor->resize(config_.lock()->getInstrumentDrumkitWindowWidth(), config_.lock()->getInstrumentDrumkitWindowHeight()); QObject::connect(kitEditor, &AdpcmDrumkitEditor::sampleNumberChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentADPCMSampleNumberChanged); QObject::connect(kitEditor, &AdpcmDrumkitEditor::sampleParameterChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentADPCMSampleParameterChanged); QObject::connect(kitEditor, &AdpcmDrumkitEditor::sampleAssignRequested, this, &MainWindow::assignADPCMSamples); QObject::connect(kitEditor, &AdpcmDrumkitEditor::sampleMemoryChanged, instDialogMan_.get(), &InstrumentEditorManager::onInstrumentADPCMSampleMemoryUpdated); QObject::connect(kitEditor, &AdpcmDrumkitEditor::jamKeyOnEvent, this, [&](JamKey key) { bt_->jamKeyOnForced(key, SoundSource::ADPCM, !config_.lock()->getFixJammingVolume()); }, Qt::DirectConnection); QObject::connect(kitEditor, &AdpcmDrumkitEditor::jamKeyOffEvent, this, [&](JamKey key) { bt_->jamKeyOffForced(key, SoundSource::ADPCM); }, Qt::DirectConnection); QObject::connect(kitEditor, &AdpcmDrumkitEditor::modified, this, &MainWindow::setModifiedTrue); kitEditor->installEventFilter(this); instDialogMan_->onInstrumentADPCMSampleNumberChanged(); break; } default: throw std::invalid_argument("Invalid instrument type"); } editor->addActions({ &octUpSc_, &octDownSc_, &jamVolUpSc_, &jamVolDownSc_ }); instDialogMan_->add(num, editor); } instDialogMan_->showEditor(num); } int MainWindow::findRowFromInstrumentList(int instNum) { auto& list = ui->instrumentList; int row = 0; for (; row < list->count(); ++row) { auto item = list->item(row); if (item->data(Qt::UserRole).toInt() == instNum) break; } return row; } void MainWindow::renameInstrument() { auto list = ui->instrumentList; auto item = list->currentItem(); // Finish current edit if (item == renamingInstItem_) { finishRenamingInstrument(); return; } else if (renamingInstItem_) { finishRenamingInstrument(); } renamingInstItem_ = item; int num = item->data(Qt::UserRole).toInt(); renamingInstEdit_ = new QLineEdit(gui_utils::utf8ToQString(bt_->getInstrument(num)->getName())); QObject::connect(renamingInstEdit_, &QLineEdit::editingFinished, this, &MainWindow::finishRenamingInstrument); renamingInstEdit_->installEventFilter(this); ui->instrumentList->setItemWidget(item, renamingInstEdit_); renamingInstEdit_->selectAll(); renamingInstEdit_->setFocus(); } void MainWindow::finishRenamingInstrument() { if (!renamingInstItem_ || !renamingInstEdit_) return; bool hasFocus = renamingInstEdit_->hasFocus(); auto list = ui->instrumentList; int num = renamingInstItem_->data(Qt::UserRole).toInt(); int row = findRowFromInstrumentList(num); auto oldName = gui_utils::utf8ToQString(bt_->getInstrument(num)->getName()); QString newName = renamingInstEdit_->text(); list->removeItemWidget(renamingInstItem_); if (newName != oldName) { bt_->setInstrumentName(num, newName.toUtf8().toStdString()); comStack_->push(new ChangeInstrumentNameQtCommand(list, num, row, instDialogMan_, oldName, newName)); } renamingInstItem_ = nullptr; renamingInstEdit_ = nullptr; if (hasFocus) ui->instrumentList->setFocus(); } void MainWindow::cloneInstrument() { int num = bt_->findFirstFreeInstrumentNumber(); if (num == -1) return; int refNum = ui->instrumentList->currentItem()->data(Qt::UserRole).toInt(); // KEEP CODE ORDER // bt_->cloneInstrument(num, refNum); auto inst = bt_->getInstrument(num); comStack_->push(new CloneInstrumentQtCommand(ui->instrumentList, num, inst->getType(), gui_utils::utf8ToQString(inst->getName()), instDialogMan_)); //----------// } void MainWindow::deepCloneInstrument() { int num = bt_->findFirstFreeInstrumentNumber(); if (num == -1) return; int refNum = ui->instrumentList->currentItem()->data(Qt::UserRole).toInt(); // KEEP CODE ORDER // bt_->deepCloneInstrument(num, refNum); auto inst = bt_->getInstrument(num); comStack_->push(new DeepCloneInstrumentQtCommand( ui->instrumentList, num, inst->getType(), gui_utils::utf8ToQString(inst->getName()), instDialogMan_, this, config_.lock()->getWriteOnlyUsedSamples())); //----------// } void MainWindow::loadInstrument() { QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory()); std::vector orgFilters = io::InstrumentIO::getInstance().getLoadFilter(); QStringList filters; std::transform(orgFilters.begin(), orgFilters.end(), std::back_inserter(filters), [](const std::string& f) { return QString::fromStdString(f); }); QString selectedFilter = filters.at(config_.lock()->getInstrumentOpenFormat()); const QStringList files = QFileDialog::getOpenFileNames(this, tr("Open instrument"), (dir.isEmpty() ? "./" : dir), filters.join(";;"), &selectedFilter #if defined(Q_OS_LINUX) || (defined(Q_OS_BSD4) && !defined(Q_OS_DARWIN)) , QFileDialog::DontUseNativeDialog #endif ); if (files.empty()) return; int index = std::distance(filters.begin(), utils::find(filters, selectedFilter)); config_.lock()->setInstrumentOpenFormat(index); for (const QString& file : files) funcLoadInstrument(file); } void MainWindow::funcLoadInstrument(QString file) { int n = bt_->findFirstFreeInstrumentNumber(); if (n == -1) { FileIOErrorMessageBox(file, true, io::FileType::Inst, tr("The number of instruments has reached the upper limit."), this).exec(); return; } try { io::BinaryContainer container; { QFile fp(file); if (!fp.open(QIODevice::ReadOnly)) { FileIOErrorMessageBox::openError(file, true, io::FileType::Inst, this); return; } QByteArray&& array = fp.readAll(); fp.close(); std::move(array.begin(), array.end(), std::back_inserter(container)); } bt_->loadInstrument(container, file.toStdString(), n); auto inst = bt_->getInstrument(n); comStack_->push(new AddInstrumentQtCommand( ui->instrumentList, n, gui_utils::utf8ToQString(inst->getName()), inst->getType(), instDialogMan_, this, config_.lock()->getWriteOnlyUsedSamples())); ui->instrumentList->setCurrentRow(n); config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString()); } catch (io::FileIOError& e) { FileIOErrorMessageBox(file, true, e, this).exec(); } catch (std::exception& e) { FileIOErrorMessageBox(file, true, io::FileType::Inst, QString(e.what()), this).exec(); } } void MainWindow::saveInstrument() { int n = ui->instrumentList->currentItem()->data(Qt::UserRole).toInt(); QString name = gui_utils::utf8ToQString(bt_->getInstrument(n)->getName()); QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory()); std::vector orgFilters = io::InstrumentIO::getInstance().getSaveFilter(); QStringList filters; std::transform(orgFilters.begin(), orgFilters.end(), std::back_inserter(filters), [](const std::string& f) { return QString::fromStdString(f); }); QString defaultFilter = filters.front(); // bti QString file = QFileDialog::getSaveFileName( this, tr("Save instrument"), QString("%1/%2.bti").arg(dir.isEmpty() ? "." : dir, name), filters.join(";;"), &defaultFilter #if defined(Q_OS_LINUX) || (defined(Q_OS_BSD4) && !defined(Q_OS_DARWIN)) , QFileDialog::DontUseNativeDialog #endif ); if (file.isNull()) return; if (!file.endsWith(".bti")) file += ".bti"; // For linux try { QByteArray bytes; { io::BinaryContainer container; bt_->saveInstrument(container, n); bytes.reserve(container.size()); std::move(container.begin(), container.end(), std::back_inserter(bytes)); } QFile fp(file); if (!fp.open(QIODevice::WriteOnly)) { FileIOErrorMessageBox::openError(file, false, io::FileType::Inst, this); return; } fp.write(bytes); fp.close(); config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString()); } catch (io::FileIOError& e) { FileIOErrorMessageBox(file, false, e, this).exec(); } catch (std::exception& e) { FileIOErrorMessageBox(file, false, io::FileType::Inst, QString(e.what()), this).exec(); } } void MainWindow::importInstrumentsFromBank() { stopPlaySong(); QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory()); std::vector orgFilters = io::BankIO::getInstance().getLoadFilter(); QStringList filters; std::transform(orgFilters.begin(), orgFilters.end(), std::back_inserter(filters), [](const std::string& f) { return QString::fromStdString(f); }); QString selectedFilter = filters.at(config_.lock()->getBankOpenFormat()); QString file = QFileDialog::getOpenFileName(this, tr("Open bank"), (dir.isEmpty() ? "./" : dir), filters.join(";;"), &selectedFilter #if defined(Q_OS_LINUX) || (defined(Q_OS_BSD4) && !defined(Q_OS_DARWIN)) , QFileDialog::DontUseNativeDialog #endif ); if (file.isNull()) return; int index = std::distance(filters.begin(), utils::find(filters, selectedFilter)); config_.lock()->setBankOpenFormat(index); funcImportInstrumentsFromBank(file); } void MainWindow::funcImportInstrumentsFromBank(QString file) { stopPlaySong(); std::unique_ptr bank; try { io::BinaryContainer container; { QFile fp(file); if (!fp.open(QIODevice::ReadOnly)) { FileIOErrorMessageBox::openError(file, true, io::FileType::Bank, this); return; } QByteArray&& array = fp.readAll(); fp.close(); std::move(array.begin(), array.end(), std::back_inserter(container)); } bank.reset(io::BankIO::getInstance().loadBank(container, file.toStdString())); config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString()); } catch (io::FileIOError& e) { FileIOErrorMessageBox(file, true, e, this).exec(); return; } catch (std::exception& e) { FileIOErrorMessageBox(file, true, io::FileType::Bank, QString(e.what()), this).exec(); return; } // Change text codec if (auto ff = dynamic_cast(bank.get())) { QTextCodec* codec = QTextCodec::codecForName("Shift-JIS"); for (size_t i = 0; i < ff->getNumInstruments(); ++i) { std::string sjis = ff->getInstrumentName(i); std::string utf8 = codec->toUnicode(sjis.c_str(), sjis.length()).toStdString(); ff->setInstrumentName(i, utf8); } } else if (auto mu88 = dynamic_cast(bank.get())) { QTextCodec* codec = QTextCodec::codecForName("Shift-JIS"); for (size_t i = 0; i < mu88->getNumInstruments(); ++i) { std::string sjis = mu88->getInstrumentName(i); std::string utf8 = codec->toUnicode(sjis.c_str(), sjis.length()).toStdString(); mu88->setInstrumentName(i, utf8); } } size_t jamId = 128; // Dummy std::shared_ptr jamInst; importBankDialog_ = std::make_unique(*bank, tr("Select instruments to load:"), config_, this); auto bankMan = std::make_shared(true); auto updateInst = [&] (size_t id) { if (id != jamId) { bankJamMidiCtrl_.store(true); jamId = id; bankMan->clearAll(); jamInst.reset(bank->loadInstrument(id, bankMan, 0)); jamInst->setNumber(128); // Special number std::unordered_map> sampNums; bt_->assignADPCMBeforeForcedJamKeyOn(jamInst, sampNums); for (const auto& pairs : sampNums) { bankMan->setSampleADPCMStartAddress(pairs.first, pairs.second[0]); bankMan->setSampleADPCMStopAddress(pairs.first, pairs.second[1]); } bankJamMidiCtrl_.store(false); } }; QObject::connect(importBankDialog_.get(), &InstrumentSelectionDialog::jamKeyOnEvent, this, [&](size_t id, JamKey key) { updateInst(id); bt_->jamKeyOnForced(key, jamInst->getSoundSource(), !config_.lock()->getFixJammingVolume(), jamInst); }, Qt::DirectConnection); QObject::connect(importBankDialog_.get(), &InstrumentSelectionDialog::jamKeyOnMidiEvent, this, [&](size_t id, int key) { updateInst(id); bt_->jamKeyOnForced(key, jamInst->getSoundSource(), !config_.lock()->getFixJammingVolume(), jamInst); }, Qt::DirectConnection); QObject::connect(importBankDialog_.get(), &InstrumentSelectionDialog::jamKeyOffEvent, this, [&](JamKey key) { bt_->jamKeyOffForced(key, jamInst->getSoundSource()); }, Qt::DirectConnection); QObject::connect(importBankDialog_.get(), &InstrumentSelectionDialog::jamKeyOffMidiEvent, this, [&](int key) { if (jamInst) bt_->jamKeyOffForced(key, jamInst->getSoundSource()); }, Qt::DirectConnection); importBankDialog_->addActions({ &octUpSc_, &octDownSc_ }); if (importBankDialog_->exec() != QDialog::Accepted) { assignADPCMSamples(); // Restore importBankDialog_.reset(); return; } const QVector selection = importBankDialog_->currentInstrumentSelection(); importBankDialog_.reset(); if (selection.empty()) return; try { bool sampleRestoreRequested = false; int lastNum = ui->instrumentList->currentRow(); for (const size_t& index : selection) { int n = bt_->findFirstFreeInstrumentNumber(); if (n == -1){ FileIOErrorMessageBox(file, true, io::FileType::Inst, tr("The number of instruments has reached the upper limit."), this).exec(); ui->instrumentList->setCurrentRow(lastNum); return; } bt_->importInstrument(*bank, index, n); auto inst = bt_->getInstrument(n); comStack_->push(new AddInstrumentQtCommand( ui->instrumentList, n, gui_utils::utf8ToQString(inst->getName()), inst->getType(), instDialogMan_, this, config_.lock()->getWriteOnlyUsedSamples(), true)); lastNum = n; sampleRestoreRequested |= (inst->getSoundSource() == SoundSource::ADPCM); } ui->instrumentList->setCurrentRow(lastNum); if (sampleRestoreRequested) assignADPCMSamples(); // Store only once } catch (io::FileIOError& e) { FileIOErrorMessageBox(file, true, e, this).exec(); } catch (std::exception& e) { FileIOErrorMessageBox(file, true, io::FileType::Bank, QString(e.what()), this).exec(); } } void MainWindow::exportInstrumentsToBank() { std::vector ids = bt_->getInstrumentIndices(); std::shared_ptr bank(std::make_shared(ids, bt_->getInstrumentNames())); InstrumentSelectionDialog dlg(*bank, tr("Select instruments to save:"), config_, this); if (dlg.exec() != QDialog::Accepted) return; QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory()); std::vector orgFilters = io::BankIO::getInstance().getSaveFilter(); QStringList filters; std::transform(orgFilters.begin(), orgFilters.end(), std::back_inserter(filters), [](const std::string& f) { return QString::fromStdString(f); }); QString defaultFilter = filters.front(); // btb QString file = QFileDialog::getSaveFileName(this, tr("Save bank"), (dir.isEmpty() ? "./" : dir), filters.join(";;"), &defaultFilter #if defined(Q_OS_LINUX) || (defined(Q_OS_BSD4) && !defined(Q_OS_DARWIN)) , QFileDialog::DontUseNativeDialog #endif ); if (file.isNull()) return; QVector selection = dlg.currentInstrumentSelection(); if (selection.empty()) return; std::vector sel; std::transform(selection.begin(), selection.end(), std::back_inserter(sel), [&ids](size_t i) { return ids.at(i); }); std::sort(sel.begin(), sel.end()); try { QByteArray bytes; { io::BinaryContainer container; bt_->exportInstruments(container, sel); bytes.reserve(container.size()); std::move(container.begin(), container.end(), std::back_inserter(bytes)); } QFile fp(file); if (!fp.open(QIODevice::WriteOnly)) { FileIOErrorMessageBox::openError(file, false, io::FileType::Bank, this); return; }fp.write(bytes); fp.close(); config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString()); } catch (io::FileIOError& e) { FileIOErrorMessageBox(file, false, e, this).exec(); } catch (std::exception& e) { FileIOErrorMessageBox(file, false, io::FileType::Bank, QString(e.what()), this).exec(); } } void MainWindow::swapInstruments(int row1, int row2) { if (row1 == row2) return; // KEEP CODE ORDER // int num1 = ui->instrumentList->item(row1)->data(Qt::UserRole).toInt(); int num2 = ui->instrumentList->item(row2)->data(Qt::UserRole).toInt(); QString name1 = gui_utils::utf8ToQString(bt_->getInstrument(num1)->getName()); QString name2 = gui_utils::utf8ToQString(bt_->getInstrument(num2)->getName()); bt_->swapInstruments(num1, num2, config_.lock()->getReflectInstrumentNumberChange()); comStack_->push(new SwapInstrumentsQtCommand( ui->instrumentList, row1, row2, name1, name2, instDialogMan_, ui->patternEditor)); //----------// } /********** Undo-Redo **********/ void MainWindow::undo() { if (!bt_->undo()) { command_result_message_box::showCommandUndoingErrorMessageBox(this); return; } comStack_->undo(); } void MainWindow::redo() { if (!bt_->redo()) { command_result_message_box::showCommandRedoingErrorMessageBox(this); return; } comStack_->redo(); } /********** Load data **********/ void MainWindow::loadModule() { instDialogMan_->removeAll(); ui->instrumentList->clear(); on_instrumentList_itemSelectionChanged(); ui->modTitleLineEdit->setText(gui_utils::utf8ToQString(bt_->getModuleTitle())); ui->modTitleLineEdit->setCursorPosition(0); ui->authorLineEdit->setText(gui_utils::utf8ToQString(bt_->getModuleAuthor())); ui->authorLineEdit->setCursorPosition(0); ui->copyrightLineEdit->setText(gui_utils::utf8ToQString(bt_->getModuleCopyright())); ui->copyrightLineEdit->setCursorPosition(0); { QSignalBlocker blocker(ui->songComboBox); // Prevent duplicated call "loadSong" ui->songComboBox->clear(); for (size_t i = 0; i < bt_->getSongCount(); ++i) { QString title = gui_utils::utf8ToQString(bt_->getSongTitle(static_cast(i))); if (title.isEmpty()) title = tr("Untitled"); ui->songComboBox->addItem(QString("#%1 %2").arg(i).arg(title)); } } highlight1_->setValue(static_cast(bt_->getModuleStepHighlight1Distance())); highlight2_->setValue(static_cast(bt_->getModuleStepHighlight2Distance())); for (auto& idx : bt_->getInstrumentIndices()) { auto inst = bt_->getInstrument(idx); comStack_->push(new AddInstrumentQtCommand( ui->instrumentList, idx, gui_utils::utf8ToQString(inst->getName()), inst->getType(), instDialogMan_, this, config_.lock()->getWriteOnlyUsedSamples(), true)); } isSavedModBefore_ = false; loadSong(); // Set tick frequency stream_->setInterruption(bt_->getModuleTickFrequency()); if (tickTimerForRealChip_) tickTimerForRealChip_->setInterval(1000000 / bt_->getModuleTickFrequency()); statusIntr_->setText(QString::number(bt_->getModuleTickFrequency()) + QString("Hz")); // Set mixer QString text; switch (bt_->getModuleMixerType()) { case MixerType::UNSPECIFIED: bt_->setMasterVolumeFM(config_.lock()->getMixerVolumeFM()); bt_->setMasterVolumeSSG(config_.lock()->getMixerVolumeSSG()); text = tr("-"); break; case MixerType::CUSTOM: bt_->setMasterVolumeFM(bt_->getModuleCustomMixerFMLevel()); bt_->setMasterVolumeSSG(bt_->getModuleCustomMixerSSGLevel()); text = tr("Custom"); break; case MixerType::PC_9821_PC_9801_86: bt_->setMasterVolumeFM(0); bt_->setMasterVolumeSSG(-5.5); text = tr("PC-9821 with PC-9801-86"); break; case MixerType::PC_9821_SPEAK_BOARD: bt_->setMasterVolumeFM(0); bt_->setMasterVolumeSSG(-3.0); text = tr("PC-9821 with Speak Board"); break; case MixerType::PC_8801_VA2: bt_->setMasterVolumeFM(0); bt_->setMasterVolumeSSG(1.5); text = tr("PC-88VA2"); break; case MixerType::PC_8801_MKII_SR: bt_->setMasterVolumeFM(0); bt_->setMasterVolumeSSG(2.5); text = tr("NEC PC-8801mkIISR"); break; } statusMixer_->setText(text); // Set comment if (commentDialog_) commentDialog_->setComment(gui_utils::utf8ToQString(bt_->getModuleComment())); // Clear records QApplication::clipboard()->clear(); comStack_->clear(); bt_->clearCommandHistory(); } void MainWindow::openModule(const QString& file) { try { freezeViews(); if (tickTimerForRealChip_) tickTimerForRealChip_->stop(); else { try { stream_->stop(); } catch (std::exception& e) { showStreamFailedDialog(e.what()); } } QFile fp(file); if (fp.open(QIODevice::ReadOnly)) { io::BinaryContainer container; { QByteArray&& array = fp.readAll(); fp.close(); std::move(array.begin(), array.end(), std::back_inserter(container)); } bt_->loadModule(container); bt_->setModulePath(file.toStdString()); loadModule(); config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString()); changeFileHistory(file); goto AFTER_MOD_LOADING; // Skip error handling section } else { FileIOErrorMessageBox::openError(file, true, io::FileType::Mod, this); } } catch (std::exception& e) { if (auto ef = dynamic_cast(&e)) { FileIOErrorMessageBox(file, true, *ef, this).exec(); } else { FileIOErrorMessageBox(file, true, io::FileType::Mod, QString(e.what()), this).exec(); } } // Init module as a plain when something is wrong freezeViews(); bt_->makeNewModule(); loadModule(); AFTER_MOD_LOADING: // Post process of module loading isModifiedForNotCommand_ = false; setWindowModified(false); if (tickTimerForRealChip_) tickTimerForRealChip_->start(); else { try { stream_->start(); } catch (std::exception& e) { showStreamFailedDialog(e.what()); } } setInitialSelectedInstrument(); assignADPCMSamples(); } void MainWindow::loadSong() { // Init position int songCnt = static_cast(bt_->getSongCount()); if (ui->songComboBox->currentIndex() >= songCnt) bt_->setCurrentSongNumber(songCnt - 1); else bt_->setCurrentSongNumber(bt_->getCurrentSongNumber()); bt_->setCurrentOrderNumber(0); bt_->setCurrentTrack(0); bt_->setCurrentStepNumber(0); // Init ui ui->orderList->onSongLoaded(); setOrderListGroupMaximumWidth(); ui->patternEditor->onSongLoaded(); unfreezeViews(); int curSong = bt_->getCurrentSongNumber(); ui->songComboBox->setCurrentIndex(curSong); ui->tempoSpinBox->setValue(bt_->getSongTempo(curSong)); ui->speedSpinBox->setValue(bt_->getSongSpeed(curSong)); ui->patternSizeSpinBox->setValue(static_cast(bt_->getDefaultPatternSize(curSong))); ui->grooveSpinBox->setValue(bt_->getSongGroove(curSong)); ui->grooveSpinBox->setMaximum(static_cast(bt_->getGrooveCount()) - 1); if (bt_->isUsedTempoInSong(curSong)) { ui->tempoSpinBox->setEnabled(true); ui->speedSpinBox->setEnabled(true); ui->grooveCheckBox->setChecked(false); ui->grooveSpinBox->setEnabled(false); } else { ui->tempoSpinBox->setEnabled(false); ui->speedSpinBox->setEnabled(false); ui->grooveCheckBox->setChecked(true); ui->grooveSpinBox->setEnabled(true); } onCurrentTrackChanged(); setWindowTitle(); switch (bt_->getSongStyle(bt_->getCurrentSongNumber()).type) { case SongType::Standard: statusStyle_->setText(tr("Standard")); break; case SongType::FM3chExpanded: statusStyle_->setText(tr("FM3ch expanded")); break; } statusPlayPos_->setText(config_.lock()->getShowRowNumberInHex() ? "00/00" : "000/000"); bmManForm_->onCurrentSongNumberChanged(); ksManForm_->onCurrentSongNumberChanged(); // Update track visibility std::vector visTracks; int all = static_cast(bt_->getSongStyle(curSong).trackAttribs.size()); for (int i = 0; i < all; ++i) { if (bt_->isVisibleTrack(curSong, i)) visTracks.push_back(i); } ui->orderList->setVisibleTracks(visTracks); setOrderListGroupMaximumWidth(); ui->patternEditor->setVisibleTracks(visTracks); } void MainWindow::assignADPCMSamples() { bt_->stopPlaySong(); lockWidgets(false); if (tickTimerForRealChip_) tickTimerForRealChip_->stop(); else { try { stream_->stop(); } catch (std::exception& e) { showStreamFailedDialog(e.what()); } } bool isStoredAll = bt_->assignSampleADPCMRawSamples(); // Mutex register instDialogMan_->onInstrumentADPCMSampleMemoryUpdated(); if (!isStoredAll) { QMessageBox::warning(this, tr("Warning"), tr("Insufficient memory size to load ADPCM samples. Please delete the unused samples.")); } if (tickTimerForRealChip_) tickTimerForRealChip_->start(); else { try { stream_->start(); } catch (std::exception& e) { showStreamFailedDialog(e.what()); } } } /********** Play song **********/ void MainWindow::startPlaySong() { bt_->startPlaySong(); lockWidgets(true); firstViewUpdateRequest_ = true; } void MainWindow::startPlayFromStart() { bt_->startPlayFromStart(); lockWidgets(true); firstViewUpdateRequest_ = true; } void MainWindow::startPlayPattern() { bt_->startPlayPattern(); lockWidgets(true); firstViewUpdateRequest_ = true; } void MainWindow::startPlayFromCurrentStep() { bt_->startPlayFromCurrentStep(); lockWidgets(true); firstViewUpdateRequest_ = true; } void MainWindow::startPlayFromMarker() { if (bt_->startPlayFromMarker()) { lockWidgets(true); firstViewUpdateRequest_ = true; } } void MainWindow::playStep() { if (!bt_->isPlaySong()) { bt_->playStep(); firstViewUpdateRequest_ = true; ui->patternEditor->onPlayStepPressed(); } } void MainWindow::stopPlaySong() { bt_->stopPlaySong(); lockWidgets(false); ui->patternEditor->onStoppedPlaySong(); ui->orderList->onStoppedPlaySong(); } void MainWindow::lockWidgets(bool isLock) { hasLockedWigets_ = isLock; ui->songComboBox->setEnabled(!isLock); } /********** Octave change **********/ void MainWindow::changeOctave(bool upFlag) { if (upFlag) octave_->stepUp(); else octave_->stepDown(); statusOctave_->setText(tr("Octave: %1").arg(bt_->getCurrentOctave())); } /********** Configuration change **********/ void MainWindow::changeConfiguration() { // Real chip interface bool streamState = false; RealChipInterfaceType intf = config_.lock()->getRealChipInterface(); if (intf == RealChipInterfaceType::NONE) { tickTimerForRealChip_.reset(); bt_->connectToRealChip(RealChipInterfaceType::NONE); try { QString streamErr; streamState = stream_->initialize( config_.lock()->getSampleRate(), config_.lock()->getBufferLength(), bt_->getModuleTickFrequency(), gui_utils::utf8ToQString(config_.lock()->getSoundAPI()), gui_utils::utf8ToQString(config_.lock()->getSoundDevice()), &streamErr); if (!streamState) showStreamFailedDialog(streamErr); } catch (std::exception& e) { showStreamFailedDialog(e.what()); } try { stream_->start(); } catch (std::exception& e) { showStreamFailedDialog(e.what()); } } else { try { stream_->stop(); } catch (std::exception& e) { showStreamFailedDialog(e.what()); } if (tickTimerForRealChip_) { tickTimerForRealChip_->stop(); } else { tickTimerForRealChip_ = std::make_unique(); tickTimerForRealChip_->setInterval(1000000 / bt_->getModuleTickFrequency()); tickEventMethod_ = metaObject()->indexOfSlot("onNewTickSignaledRealChip()"); Q_ASSERT(tickEventMethod_ != -1); tickTimerForRealChip_->setFunction([&]{ QMetaMethod method = this->metaObject()->method(this->tickEventMethod_); method.invoke(this, Qt::QueuedConnection); }); } setRealChipInterface(intf); tickTimerForRealChip_->start(); } setMidiConfiguration(); NoteNameManager::getManager().setNotationSystem(config_.lock()->getNotationSystem()); updateFonts(); ui->orderList->setHorizontalScrollMode(config_.lock()->getMoveCursorByHorizontalScroll()); ui->patternEditor->setHorizontalScrollMode(config_.lock()->getMoveCursorByHorizontalScroll()); instDialogMan_->updateByConfiguration(); bt_->changeConfiguration(config_); if (streamState) { uint32_t sr = stream_->getStreamRate(); if (config_.lock()->getSampleRate() != sr) { showStreamRateWarningDialog(sr); bt_->setStreamRate(sr); } } setShortcuts(); updateInstrumentListColors(); bmManForm_->onConfigurationChanged(config_.lock()->getShowRowNumberInHex()); ksManForm_->onConfigurationChanged(config_.lock()->getShowRowNumberInHex()); visualTimer_->stop(); visualTimer_->start(static_cast(std::round(1000. / config_.lock()->getWaveViewFrameRate()))); update(); } void MainWindow::setRealChipInterface(RealChipInterfaceType intf) { if (intf == bt_->getRealChipInterfaceType()) return; if (isWindowModified() && QMessageBox::warning(this, tr("Warning"), tr("The module has been changed. Do you want to save it?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { on_actionSave_As_triggered(); } struct Pair { std::weak_ptr lib; const char* symbol; }; static std::unordered_map RCI_DIC = { { RealChipInterfaceType::SCCI, { scciDll_, "getSoundInterfaceManager" } }, { RealChipInterfaceType::C86CTL, { c86ctlDll_, "CreateInstance" } } }; auto& dll = RCI_DIC.at(intf).lib; dll.lock()->load(); if (dll.lock()->isLoaded()) bt_->connectToRealChip(intf, new RealChipInterfaceGeneratorFunc(dll.lock()->resolve(RCI_DIC[intf].symbol))); else bt_->connectToRealChip(RealChipInterfaceType::NONE); bt_->assignSampleADPCMRawSamples(); // Mutex register instDialogMan_->onInstrumentADPCMSampleMemoryUpdated(); } void MainWindow::setMidiConfiguration() { MidiInterface &midiIntf = MidiInterface::getInstance(); std::string midiApi = config_.lock()->getMidiAPI(); std::string midiInPortName = config_.lock()->getMidiInputPort(); if (config_.lock()->getMidiEnabled()) { std::string errDetail; if (midiApi.empty()) { config_.lock()->setMidiEnabled(false); midiIntf.switchApi(""); // Clear } else { if (!midiIntf.isAvailableApi(midiApi)) { showMidiFailedDialog("Invalid API name."); midiIntf.switchApi(""); // Clear return; } if (midiIntf.switchApi(midiApi, &errDetail)) { bool resPort = true; if (!midiInPortName.empty()) resPort = midiIntf.openInputPortByName(midiInPortName, &errDetail); else if (midiIntf.supportsVirtualPort()) resPort = midiIntf.openInputPort(~0u, &errDetail); else config_.lock()->setMidiEnabled(false); if (!resPort) { showMidiFailedDialog(QString::fromStdString(errDetail)); midiIntf.switchApi(""); // Clear } } else { showMidiFailedDialog(QString::fromStdString(errDetail)); midiIntf.switchApi(""); // Clear } } } } void MainWindow::updateFonts() { QFont ptnHeader, ptnRows, odrHeader, odrRows; if (!ptnHeader.fromString(gui_utils::utf8ToQString(config_.lock()->getPatternEditorHeaderFont()))) { ptnHeader = ui->patternEditor->getDefaultHeaderFont(); config_.lock()->setPatternEditorHeaderFont(ptnHeader.toString().toUtf8().toStdString()); } if (!ptnRows.fromString(gui_utils::utf8ToQString(config_.lock()->getPatternEditorRowsFont()))) { ptnRows = ui->patternEditor->getDefaultRowsFont(); config_.lock()->setPatternEditorRowsFont(ptnRows.toString().toUtf8().toStdString()); } if (!odrHeader.fromString(gui_utils::utf8ToQString(config_.lock()->getOrderListHeaderFont()))) { odrHeader = ui->orderList->getDefaultHeaderFont(); config_.lock()->setOrderListHeaderFont(odrHeader.toString().toUtf8().toStdString()); } if (!odrRows.fromString(gui_utils::utf8ToQString(config_.lock()->getOrderListRowsFont()))) { odrRows = ui->orderList->getDefaultRowsFont(); config_.lock()->setOrderListRowsFont(odrRows.toString().toUtf8().toStdString()); } ui->patternEditor->setFonts(ptnHeader, ptnRows); ui->orderList->setFonts(odrHeader, odrRows); } /********** History change **********/ void MainWindow::changeFileHistory(QString file) { fileHistory_->addFile(file); for (int i = ui->menu_Recent_Files->actions().count() - 1; 1 < i; --i) ui->menu_Recent_Files->removeAction(ui->menu_Recent_Files->actions().at(i)); for (size_t i = 0; i < fileHistory_->size(); ++i) { // Leave Before Qt5.7.0 style due to windows xp QAction* action = ui->menu_Recent_Files->addAction(QString("&%1 %2").arg(i + 1).arg(fileHistory_->at(i))); action->setData(fileHistory_->at(i)); } } /********** Backup **********/ bool MainWindow::backupModule(QString srcFile) { if (!isSavedModBefore_ && config_.lock()->getBackupModules()) { bool err = false; QString backup = srcFile + ".bak"; if (QFile::exists(backup)) err = !QFile::remove(backup); if (err || !QFile::copy(srcFile, backup)) { QMessageBox::critical(this, tr("Error"), tr("Failed to backup module.")); return false; } } return true; } /******************************/ void MainWindow::setWindowTitle() { int n = bt_->getCurrentSongNumber(); QString filePath = QString::fromStdString(bt_->getModulePath()); QString fileName = filePath.isEmpty() ? tr("Untitled") : QFileInfo(filePath).fileName(); QString songTitle = gui_utils::utf8ToQString(bt_->getSongTitle(n)); if (songTitle.isEmpty()) songTitle = tr("Untitled"); QMainWindow::setWindowTitle(QString("%1[*] [#%2 %3] - BambooTracker") .arg(fileName, QString::number(n), songTitle)); } void MainWindow::setModifiedTrue() { isModifiedForNotCommand_ = true; setWindowModified(true); } void MainWindow::setInitialSelectedInstrument() { if (bt_->getInstrumentIndices().empty()) { bt_->setCurrentInstrument(-1); statusInst_->setText(tr("No instrument")); } else { ui->instrumentList->setCurrentRow(0); } } QString MainWindow::getModuleFileBaseName() const { auto filePathStd = bt_->getModulePath(); QString filePath = QString::fromStdString(filePathStd); return (filePath.isEmpty() ? tr("Untitled") : QFileInfo(filePath).completeBaseName()); } /******************************/ /********** Instrument list events **********/ void MainWindow::on_instrumentList_customContextMenuRequested(const QPoint &pos) { auto& list = ui->instrumentList; QPoint globalPos = list->mapToGlobal(pos); QMenu menu; // Leave Before Qt5.7.0 style due to windows xp menu.addActions({ ui->actionNew_Instrument, ui->actionNew_Drumki_t, ui->actionRemove_Instrument }); menu.addSeparator(); menu.addAction(ui->actionRename_Instrument); menu.addSeparator(); menu.addActions({ ui->actionClone_Instrument, ui->actionDeep_Clone_Instrument }); menu.addSeparator(); menu.addActions({ ui->actionLoad_From_File, ui->actionSave_To_File }); menu.addSeparator(); menu.addActions({ ui->actionImport_From_Bank_File, ui->actionExport_To_Bank_File }); menu.addSeparator(); menu.addAction(ui->actionEdit); menu.exec(globalPos); } void MainWindow::on_instrumentList_itemDoubleClicked(QListWidgetItem *) { openInstrumentEditor(); } void MainWindow::on_instrumentList_itemSelectionChanged() { int num = (ui->instrumentList->currentRow() == -1) ? -1 : ui->instrumentList->currentItem()->data(Qt::UserRole).toInt(); bt_->setCurrentInstrument(num); if (num == -1) statusInst_->setText(tr("No instrument")); else statusInst_->setText( tr("Instrument: %1").arg(QString("%1").arg(num, 2, 16, QChar('0')).toUpper())); bool canAdd = (bt_->findFirstFreeInstrumentNumber() != -1); ui->actionLoad_From_File->setEnabled(canAdd); ui->actionImport_From_Bank_File->setEnabled(canAdd); bool isSelected = (num != -1); ui->actionRemove_Instrument->setEnabled(isSelected); ui->actionClone_Instrument->setEnabled(isSelected); ui->actionDeep_Clone_Instrument->setEnabled(isSelected); ui->actionSave_To_File->setEnabled(isSelected); ui->actionExport_To_Bank_File->setEnabled(isSelected); ui->actionRename_Instrument->setEnabled(isSelected); ui->actionEdit->setEnabled(isSelected); } void MainWindow::on_grooveCheckBox_stateChanged(int arg1) { if (arg1 == Qt::Checked) { ui->tempoSpinBox->setEnabled(false); ui->speedSpinBox->setEnabled(false); ui->grooveSpinBox->setEnabled(true); bt_->toggleTempoOrGrooveInSong(bt_->getCurrentSongNumber(), false); } else { ui->tempoSpinBox->setEnabled(true); ui->speedSpinBox->setEnabled(true); ui->grooveSpinBox->setEnabled(false); bt_->toggleTempoOrGrooveInSong(bt_->getCurrentSongNumber(), true); } setModifiedTrue(); } void MainWindow::on_actionExit_triggered() { close(); } void MainWindow::on_actionUndo_triggered() { undo(); } void MainWindow::on_actionRedo_triggered() { redo(); } void MainWindow::on_actionCut_triggered() { if (isEditedPattern_) ui->patternEditor->cutSelectedCells(); } void MainWindow::on_actionCopy_triggered() { if (isEditedPattern_) ui->patternEditor->copySelectedCells(); else if (isEditedOrder_) ui->orderList->copySelectedCells(); } void MainWindow::on_actionPaste_triggered() { if (isEditedPattern_) ui->patternEditor->onPastePressed(); else if (isEditedOrder_) ui->orderList->onPastePressed(); } void MainWindow::on_actionDelete_triggered() { if (isEditedPattern_) ui->patternEditor->onDeletePressed(); else if (isEditedOrder_) ui->orderList->deleteOrder(); else if (isEditedInstList_) on_actionRemove_Instrument_triggered(); } void MainWindow::updateMenuByPattern() { isEditedPattern_ = true; isEditedOrder_ = false; isEditedInstList_ = false; if (bt_->isJamMode()) { // Edit ui->actionPaste->setEnabled(false); ui->actionMix->setEnabled(false); ui->actionOverwrite->setEnabled(false); ui->action_Insert->setEnabled(false); ui->actionDelete->setEnabled(false); // Pattern ui->actionInterpolate->setEnabled(false); ui->actionReverse->setEnabled(false); ui->actionReplace_Instrument->setEnabled(false); ui->actionExpand->setEnabled(false); ui->actionShrink->setEnabled(false); ui->actionDecrease_Note->setEnabled(false); ui->actionIncrease_Note->setEnabled(false); ui->actionDecrease_Octave->setEnabled(false); ui->actionIncrease_Octave->setEnabled(false); ui->actionFine_Decrease_Values->setEnabled(false); ui->actionFine_Increase_Values->setEnabled(false); ui->actionCoarse_D_ecrease_Values->setEnabled(false); ui->actionCoarse_I_ncrease_Values->setEnabled(false); } else { // Edit bool enabled = QApplication::clipboard()->text().startsWith("PATTERN_"); ui->actionPaste->setEnabled(enabled); ui->actionMix->setEnabled(enabled); ui->actionOverwrite->setEnabled(enabled); ui->action_Insert->setEnabled(enabled); ui->actionDelete->setEnabled(true); // Pattern ui->actionInterpolate->setEnabled(isSelectedPattern_); ui->actionReverse->setEnabled(isSelectedPattern_); ui->actionReplace_Instrument->setEnabled( isSelectedPattern_ && ui->instrumentList->currentRow() != -1); ui->actionExpand->setEnabled(isSelectedPattern_); ui->actionShrink->setEnabled(isSelectedPattern_); ui->actionDecrease_Note->setEnabled(true); ui->actionIncrease_Note->setEnabled(true); ui->actionDecrease_Octave->setEnabled(true); ui->actionIncrease_Octave->setEnabled(true); ui->actionFine_Decrease_Values->setEnabled(true); ui->actionFine_Increase_Values->setEnabled(true); ui->actionCoarse_D_ecrease_Values->setEnabled(true); ui->actionCoarse_I_ncrease_Values->setEnabled(true); } updateMenuByPatternSelection(isSelectedPattern_); } void MainWindow::updateMenuByOrder() { isEditedPattern_ = false; isEditedOrder_ = true; isEditedInstList_ = false; // Edit bool enabled = QApplication::clipboard()->text().startsWith("ORDER_"); ui->actionCut->setEnabled(false); ui->actionPaste->setEnabled(enabled); ui->actionMix->setEnabled(false); ui->actionOverwrite->setEnabled(false); ui->action_Insert->setEnabled(false); ui->actionDelete->setEnabled(true); // Song bool canAdd = bt_->canAddNewOrder(bt_->getCurrentSongNumber()); ui->actionInsert_Order->setEnabled(canAdd); //ui->actionRemove_Order->setEnabled(true); ui->actionDuplicate_Order->setEnabled(canAdd); //ui->actionMove_Order_Up->setEnabled(true); //ui->actionMove_Order_Down->setEnabled(true); ui->actionClone_Patterns->setEnabled(canAdd); ui->actionClone_Order->setEnabled(canAdd); // Pattern ui->actionInterpolate->setEnabled(false); ui->actionReverse->setEnabled(false); ui->actionReplace_Instrument->setEnabled(false); ui->actionExpand->setEnabled(false); ui->actionShrink->setEnabled(false); ui->actionDecrease_Note->setEnabled(false); ui->actionIncrease_Note->setEnabled(false); ui->actionDecrease_Octave->setEnabled(false); ui->actionIncrease_Octave->setEnabled(false); ui->actionFine_Decrease_Values->setEnabled(false); ui->actionFine_Increase_Values->setEnabled(false); ui->actionCoarse_D_ecrease_Values->setEnabled(false); ui->actionCoarse_I_ncrease_Values->setEnabled(false); updateMenuByOrderSelection(isSelectedOrder_); } void MainWindow::onCurrentTrackChanged() { SoundSource src = bt_->getCurrentTrackAttribute().source; bool space = (bt_->findFirstFreeInstrumentNumber() != -1); ui->actionNew_Instrument->setEnabled((src != SoundSource::RHYTHM) && space); ui->actionNew_Drumki_t->setEnabled((src == SoundSource::ADPCM) && space); } void MainWindow::updateMenuByInstrumentList() { isEditedPattern_ = false; isEditedOrder_ = false; isEditedInstList_ = true; // Edit ui->actionPaste->setEnabled(false); ui->actionMix->setEnabled(false); ui->actionOverwrite->setEnabled(false); ui->action_Insert->setEnabled(false); ui->actionDelete->setEnabled(true); // Pattern ui->actionInterpolate->setEnabled(false); ui->actionReverse->setEnabled(false); ui->actionReplace_Instrument->setEnabled(false); ui->actionExpand->setEnabled(false); ui->actionShrink->setEnabled(false); ui->actionDecrease_Note->setEnabled(false); ui->actionIncrease_Note->setEnabled(false); ui->actionDecrease_Octave->setEnabled(false); ui->actionIncrease_Octave->setEnabled(false); ui->actionFine_Decrease_Values->setEnabled(false); ui->actionFine_Increase_Values->setEnabled(false); ui->actionCoarse_D_ecrease_Values->setEnabled(false); ui->actionCoarse_I_ncrease_Values->setEnabled(false); } void MainWindow::updateMenuByPatternSelection(bool isSelected) { isSelectedPattern_ = isSelected; if (bt_->isJamMode()) { // Edit ui->actionCopy->setEnabled(false); ui->actionCut->setEnabled(false); // Pattern ui->actionInterpolate->setEnabled(false); ui->actionReverse->setEnabled(false); ui->actionReplace_Instrument->setEnabled(false); ui->actionExpand->setEnabled(false); ui->actionShrink->setEnabled(false); } else { // Edit ui->actionCopy->setEnabled(isSelected); ui->actionCut->setEnabled(isEditedPattern_ ? isSelected : false); // Pattern bool enabled = (isEditedPattern_ && isEditedPattern_) ? isSelected : false; ui->actionInterpolate->setEnabled(enabled); ui->actionReverse->setEnabled(enabled); ui->actionReplace_Instrument->setEnabled( enabled && ui->instrumentList->currentRow() != -1); ui->actionExpand->setEnabled(enabled); ui->actionShrink->setEnabled(enabled); } } void MainWindow::updateMenuByOrderSelection(bool isSelected) { isSelectedOrder_ = isSelected; // Edit ui->actionCopy->setEnabled(isSelected); } void MainWindow::on_actionAll_triggered() { if (isEditedPattern_) ui->patternEditor->onSelectPressed(1); else if (isEditedOrder_) ui->orderList->onSelectPressed(1); } void MainWindow::on_actionNone_triggered() { if (isEditedPattern_) ui->patternEditor->onSelectPressed(0); else if (isEditedOrder_) ui->orderList->onSelectPressed(0); } void MainWindow::on_actionDecrease_Note_triggered() { if (isEditedPattern_) ui->patternEditor->onTransposePressed(false, false); } void MainWindow::on_actionIncrease_Note_triggered() { if (isEditedPattern_) ui->patternEditor->onTransposePressed(false, true); } void MainWindow::on_actionDecrease_Octave_triggered() { if (isEditedPattern_) ui->patternEditor->onTransposePressed(true, false); } void MainWindow::on_actionIncrease_Octave_triggered() { if (isEditedPattern_) ui->patternEditor->onTransposePressed(true, true); } void MainWindow::on_actionInsert_Order_triggered() { ui->orderList->insertOrderBelow(); } void MainWindow::on_actionRemove_Order_triggered() { ui->orderList->deleteOrder(); } void MainWindow::on_actionModule_Properties_triggered() { ModulePropertiesDialog dialog(bt_, config_.lock()->getMixerVolumeFM(), config_.lock()->getMixerVolumeSSG(), this); if (dialog.exec() == QDialog::Accepted && showUndoResetWarningDialog(tr("Do you want to change song properties?"))) { int instRow = ui->instrumentList->currentRow(); bt_->stopPlaySong(); lockWidgets(false); dialog.onAccepted(); freezeViews(); if (!tickTimerForRealChip_) { try { stream_->stop(); } catch (std::exception& e) { showStreamFailedDialog(e.what()); } } loadModule(); setModifiedTrue(); setWindowTitle(); ui->instrumentList->setCurrentRow(instRow); if (!tickTimerForRealChip_) { try { stream_->start(); } catch (std::exception& e) { showStreamFailedDialog(e.what()); } } assignADPCMSamples(); } } void MainWindow::on_actionNew_Instrument_triggered() { addInstrument(); } void MainWindow::on_actionRemove_Instrument_triggered() { removeInstrument(ui->instrumentList->currentRow()); } void MainWindow::on_actionClone_Instrument_triggered() { cloneInstrument(); } void MainWindow::on_actionDeep_Clone_Instrument_triggered() { deepCloneInstrument(); } void MainWindow::on_actionEdit_triggered() { openInstrumentEditor(); } void MainWindow::on_actionPlay_triggered() { startPlaySong(); } void MainWindow::on_actionPlay_Pattern_triggered() { startPlayPattern(); } void MainWindow::on_actionPlay_From_Start_triggered() { startPlayFromStart(); } void MainWindow::on_actionPlay_From_Cursor_triggered() { startPlayFromCurrentStep(); } void MainWindow::on_actionStop_triggered() { stopPlaySong(); } void MainWindow::on_actionEdit_Mode_triggered() { bt_->toggleJamMode(); ui->orderList->changeEditable(); ui->patternEditor->changeEditable(); if (isEditedOrder_) updateMenuByOrder(); else if (isEditedPattern_) updateMenuByPattern(); bool editable = !bt_->isJamMode(); ui->statusBar->showMessage(editable ? tr("Change to edit mode") : tr("Change to jam mode"), STATUS_DISPLAY_TIMEOUT); ui->action_Toggle_Bookmark->setEnabled(editable); } void MainWindow::on_actionToggle_Track_triggered() { ui->patternEditor->onToggleTrackPressed(); } void MainWindow::on_actionSolo_Track_triggered() { ui->patternEditor->onSoloTrackPressed(); } void MainWindow::on_actionKill_Sound_triggered() { bt_->killSound(); } void MainWindow::on_actionAbout_triggered() { static const QString APP_NAME = "BambooTracker v" + QString::fromStdString(Version::ofApplicationInString()); static const QString APP_DESC = tr("YM2608 Music Tracker"); static constexpr char COPY[] = "Copyright (C) 2018 Rerrah"; static const QString WEB = tr("Web:") + R"( https://bambootracker.github.io/BambooTracker/)"; static const QString LICENSE = tr("This software is licensed under the GNU General Public License v2.0 or later."); static const QString SOURCE = tr("Source is available at:"); static const QString REPO = R"(https://github.com/BambooTracker/BambooTracker)"; static const QString LIB_HEAD = tr("Libraries:"); static const QString CONTRIBUTORS = tr("Also see changelog which lists contributors."); static const QString ACKNOWLEGEMENT = tr("Thank you to everyone who reports bugs, makes suggestions, and contributes to this project!"); static const QStringList LIBS = { tr("blip_buf by (C) Shay Green (LGPL v2.1)"), #ifdef USE_REAL_CHIP tr("C86CTL by (C) honet (BSD 3-Clause)"), #endif tr("emu2149 by (C) Mitsutaka Okazaki (MIT License)"), tr("fmopn by (C) Tatsuyuki Satoh, Jarek Burczynski, ValleyBell (GPL v2+)"), tr("libOPNMIDI by (C) Vitaly Novichkov (MIT License part)"), tr("Nuked OPN-MOD by (C) Alexey Khokholov (Nuke.YKT), Jean Pierre Cimalando (LGPL v2.1+)"), tr("Qt (GPL v2+ or LGPL v3)"), tr("RtAudio by (C) Gary P. Scavone (RtAudio License)"), tr("RtMidi by (C) Gary P. Scavone (RtMidi License)"), #ifdef USE_REAL_CHIP tr("SCCI by (C) gasshi (SCCI License)"), #endif tr("Silk icons by (C) Mark James (CC BY 2.5 or 3.0)"), tr("ymdeltat by (C) Tatsuyuki Satoh, Jarek Burczynski, ValleyBell (GPL v2+)"), tr("ymfm by (C) Aaron Giles (BSD 3-Clause)") }; static const QString FORMATTED_TEXT = "

" + APP_NAME + "

" "

" + APP_DESC + "

" "

" + COPY + "

" "

" + WEB + "

" "

" + LICENSE + "
" + SOURCE + "
" + REPO + "

" "
" "

" + LIB_HEAD + "

    " + (QStringList { "" } + LIBS).join("
  • ") + "
  • " + "

" "

" + CONTRIBUTORS + "
" + ACKNOWLEGEMENT +"

"; QMessageBox dialog(QMessageBox::NoIcon, tr("About"), FORMATTED_TEXT, QMessageBox::Ok, this); dialog.setTextFormat(Qt::RichText); dialog.setIconPixmap(QIcon(":/icon/app_icon").pixmap(QSize(44, 44))); dialog.exec(); } void MainWindow::on_actionFollow_Mode_triggered() { bt_->setFollowPlay(ui->actionFollow_Mode->isChecked()); config_.lock()->setFollowMode(ui->actionFollow_Mode->isChecked()); ui->orderList->onFollowModeChanged(); ui->patternEditor->onFollowModeChanged(); } void MainWindow::on_actionGroove_Settings_triggered() { std::vector> seqs(bt_->getGrooveCount()); std::generate(seqs.begin(), seqs.end(), [&, i = 0]() mutable { return bt_->getGroove(i++); }); GrooveSettingsDialog dialog(this); dialog.setGrooveSquences(seqs); if (dialog.exec() == QDialog::Accepted) { bt_->stopPlaySong(); lockWidgets(false); bt_->setGrooves(dialog.getGrooveSequences()); ui->grooveSpinBox->setMaximum(static_cast(bt_->getGrooveCount()) - 1); setModifiedTrue(); } } void MainWindow::on_actionConfiguration_triggered() { ConfigurationDialog dialog(config_.lock(), palette_, stream_, this); QObject::connect(&dialog, &ConfigurationDialog::applyPressed, this, &MainWindow::changeConfiguration); if (dialog.exec() == QDialog::Accepted) { changeConfiguration(); io::saveConfiguration(config_.lock()); io::savePalette(palette_.get()); } } void MainWindow::on_actionExpand_triggered() { ui->patternEditor->onExpandPressed(); } void MainWindow::on_actionShrink_triggered() { ui->patternEditor->onShrinkPressed(); } void MainWindow::on_actionDuplicate_Order_triggered() { ui->orderList->onDuplicatePressed(); } void MainWindow::on_actionMove_Order_Up_triggered() { ui->orderList->onMoveOrderPressed(true); } void MainWindow::on_actionMove_Order_Down_triggered() { ui->orderList->onMoveOrderPressed(false); } void MainWindow::on_actionClone_Patterns_triggered() { ui->orderList->onClonePatternsPressed(); } void MainWindow::on_actionClone_Order_triggered() { ui->orderList->onCloneOrderPressed(); } void MainWindow::on_actionNew_triggered() { if (isWindowModified()) { switch (ModuleSaveCheckDialog(bt_->getModuleTitle(), this).exec()) { case QMessageBox::Yes: if (!on_actionSave_triggered()) return; break; case QMessageBox::No: break; case QMessageBox::Cancel: default: return; } } bt_->stopPlaySong(); lockWidgets(false); freezeViews(); if (!tickTimerForRealChip_) { try { stream_->stop(); } catch (std::exception& e) { showStreamFailedDialog(e.what()); } } bt_->makeNewModule(); loadModule(); setInitialSelectedInstrument(); isModifiedForNotCommand_ = false; setWindowModified(false); if (!tickTimerForRealChip_) { try { stream_->start(); } catch (std::exception& e) { showStreamFailedDialog(e.what()); } } assignADPCMSamples(); } void MainWindow::on_actionComments_triggered() { if (commentDialog_) { if (commentDialog_->isVisible()) commentDialog_->activateWindow(); else commentDialog_->show(); } else { commentDialog_ = std::make_unique(gui_utils::utf8ToQString(bt_->getModuleComment()), this); commentDialog_->show(); QObject::connect(commentDialog_.get(), &CommentEditDialog::commentChanged, this, [&](const QString text) { bt_->setModuleComment(text.toUtf8().toStdString()); setModifiedTrue(); }); } } bool MainWindow::on_actionSave_triggered() { auto path = QString::fromStdString(bt_->getModulePath()); if (!path.isEmpty() && QFileInfo::exists(path) && QFileInfo(path).isFile()) { if (!backupModule(path)) return false; try { QByteArray bytes; { io::BinaryContainer container; bt_->saveModule(container); bytes.reserve(container.size()); std::move(container.begin(), container.end(), std::back_inserter(bytes)); } QFile fp(path); if (!fp.open(QIODevice::WriteOnly)) { FileIOErrorMessageBox::openError(path, false, io::FileType::Mod, this); return false; } fp.write(bytes); fp.close(); isModifiedForNotCommand_ = false; isSavedModBefore_ = true; setWindowModified(false); setWindowTitle(); return true; } catch (io::FileIOError& e) { FileIOErrorMessageBox(path, false, e, this).exec(); return false; } catch (std::exception& e) { FileIOErrorMessageBox(path, false, io::FileType::Mod, QString(e.what()), this).exec(); return false; } } else { return on_actionSave_As_triggered(); } } bool MainWindow::on_actionSave_As_triggered() { QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory()); QString file = QFileDialog::getSaveFileName( this, tr("Save module"), QString("%1/%2.btm").arg(dir.isEmpty() ? "." : dir, getModuleFileBaseName()), tr("BambooTracker module (*.btm)") + ";;" + tr("All files (*)"), nullptr #if defined(Q_OS_LINUX) || (defined(Q_OS_BSD4) && !defined(Q_OS_DARWIN)) , QFileDialog::DontUseNativeDialog #endif ); if (file.isNull()) return false; if (!file.endsWith(".btm")) file += ".btm"; // For linux if (QFile::exists(file)) { // Backup if the module already exists if (!backupModule(file)) return false; } bt_->setModulePath(file.toStdString()); try { QByteArray bytes; { io::BinaryContainer container; bt_->saveModule(container); bytes.reserve(container.size()); std::move(container.begin(), container.end(), std::back_inserter(bytes)); } QFile fp(file); if (!fp.open(QIODevice::WriteOnly)) { FileIOErrorMessageBox::openError(file, false, io::FileType::Mod, this); return false; } fp.write(bytes); fp.close(); isModifiedForNotCommand_ = false; isSavedModBefore_ = true; setWindowModified(false); setWindowTitle(); config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString()); changeFileHistory(file); return true; } catch (io::FileIOError& e) { FileIOErrorMessageBox(file, false, e, this).exec(); return false; } catch (std::exception& e) { FileIOErrorMessageBox(file, false, io::FileType::Mod, QString(e.what()), this).exec(); return false; } } void MainWindow::on_actionOpen_triggered() { if (isWindowModified()) { switch (ModuleSaveCheckDialog(bt_->getModuleTitle(), this).exec()) { case QMessageBox::Yes: if (!on_actionSave_triggered()) return; break; case QMessageBox::No: break; case QMessageBox::Cancel: return; default: break; } } QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory()); QString file = QFileDialog::getOpenFileName(this, tr("Open module"), (dir.isEmpty() ? "./" : dir), tr("BambooTracker module (*.btm)") + ";;" + tr("All files (*)"), nullptr #if defined(Q_OS_LINUX) || (defined(Q_OS_BSD4) && !defined(Q_OS_DARWIN)) , QFileDialog::DontUseNativeDialog #endif ); if (file.isNull()) return; bt_->stopPlaySong(); lockWidgets(false); openModule(file); } void MainWindow::on_actionLoad_From_File_triggered() { loadInstrument(); } void MainWindow::on_actionSave_To_File_triggered() { saveInstrument(); } void MainWindow::on_actionImport_From_Bank_File_triggered() { importInstrumentsFromBank(); } void MainWindow::on_actionInterpolate_triggered() { ui->patternEditor->onInterpolatePressed(); } void MainWindow::on_actionReverse_triggered() { ui->patternEditor->onReversePressed(); } void MainWindow::on_actionReplace_Instrument_triggered() { ui->patternEditor->onReplaceInstrumentPressed(); } void MainWindow::on_actionRow_triggered() { if (isEditedPattern_) ui->patternEditor->onSelectPressed(2); else if (isEditedOrder_) ui->orderList->onSelectPressed(2); } void MainWindow::on_actionColumn_triggered() { if (isEditedPattern_) ui->patternEditor->onSelectPressed(3); else if (isEditedOrder_) ui->orderList->onSelectPressed(3); } void MainWindow::on_actionPattern_triggered() { if (isEditedPattern_) ui->patternEditor->onSelectPressed(4); else if (isEditedOrder_) ui->orderList->onSelectPressed(4); } void MainWindow::on_actionOrder_triggered() { if (isEditedPattern_) ui->patternEditor->onSelectPressed(5); else if (isEditedOrder_) ui->orderList->onSelectPressed(5); } void MainWindow::on_actionRemove_Unused_Instruments_triggered() { if (showUndoResetWarningDialog(tr("Do you want to remove all unused instruments?"))) { bt_->stopPlaySong(); lockWidgets(false); auto list = ui->instrumentList; for (auto& n : bt_->getUnusedInstrumentIndices()) { for (int i = 0; i < list->count(); ++i) { if (list->item(i)->data(Qt::UserRole).toInt() == n) { removeInstrument(i); } } } bt_->clearUnusedInstrumentProperties(); bt_->clearCommandHistory(); comStack_->clear(); setModifiedTrue(); } } void MainWindow::on_actionRemove_Unused_Patterns_triggered() { if (showUndoResetWarningDialog(tr("Do you want to remove all unused patterns?"))) { bt_->stopPlaySong(); lockWidgets(false); bt_->clearUnusedPatterns(); bt_->clearCommandHistory(); comStack_->clear(); setModifiedTrue(); } } void MainWindow::on_actionWAV_triggered() { // Record current mute states const auto& style = bt_->getSongStyle(bt_->getCurrentSongNumber()); const auto& attribs = style.trackAttribs; std::vector muteStates(attribs.size()); std::vector unmuteTracks; for (size_t i = 0; i < attribs.size(); ++i) { muteStates[i] = bt_->isMute(attribs[i].number); if (!muteStates[i]) unmuteTracks.push_back(static_cast(i)); } unmuteTracks = gui_utils::adaptVisibleTrackList(unmuteTracks, style.type, SongType::Standard); WaveExportSettingsDialog dialog(unmuteTracks, this); if (dialog.exec() != QDialog::Accepted) return; QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory()); QString path = QFileDialog::getSaveFileName( this, tr("Export to WAV"), QString("%1/%2.wav").arg(dir.isEmpty() ? "." : dir, getModuleFileBaseName()), tr("WAV signed 16-bit PCM (*.wav)") + ";;" + tr("All files (*)"), nullptr #if defined(Q_OS_LINUX) || (defined(Q_OS_BSD4) && !defined(Q_OS_DARWIN)) , QFileDialog::DontUseNativeDialog #endif ); if (path.isNull()) return; if (!path.endsWith(".wav")) path += ".wav"; // For linux QString exDir = QFileInfo(path).dir().path(); int max = static_cast(bt_->getTotalStepCount( bt_->getCurrentSongNumber(), static_cast(dialog.getLoopCount()))) + 3; QProgressDialog progress("", tr("Cancel"), 0, max, this); progress.setWindowModality(Qt::ApplicationModal); progress.setWindowFlags(progress.windowFlags() & ~Qt::WindowContextHelpButtonHint & ~Qt::WindowCloseButtonHint); auto bar = [&progress] { QApplication::processEvents(); progress.setValue(progress.value() + 1); return progress.wasCanceled(); }; unmuteTracks = dialog.getSoloExportTracks(); unmuteTracks.insert(unmuteTracks.begin(), -1); bt_->stopPlaySong(); lockWidgets(false); try { stream_->stop(); } catch (std::exception& e) { showStreamFailedDialog(e.what()); } for (size_t i = 0; i < unmuteTracks.size(); ++i) { int curTrack = unmuteTracks[i]; QString text, name; if (curTrack == -1) text = tr("Export to WAV"); // Mixed all else { if (curTrack < 6) name = gui_utils::getTrackName(SongType::Standard, SoundSource::FM, curTrack); else if (curTrack < 9) name = gui_utils::getTrackName(SongType::Standard, SoundSource::SSG, curTrack - 6); else if (curTrack < 15) name = gui_utils::getTrackName(SongType::Standard, SoundSource::RHYTHM, curTrack - 9); else name = gui_utils::getTrackName(SongType::Standard, SoundSource::ADPCM, 0); text = tr("Export %1 to WAV").arg(name); } progress.setLabelText(text); progress.setValue(0); progress.open(); // Update mute states if (i == 1) { for (const TrackAttribute& attrib : attribs) bt_->setTrackMuteState(attrib.number, attrib.number != unmuteTracks[i]); } else if (i > 1) { int prevTrack = unmuteTracks[i - 1]; if (style.type == SongType::FM3chExpanded) { if (prevTrack == 2) { bt_->setTrackMuteState(2, true); bt_->setTrackMuteState(3, true); bt_->setTrackMuteState(4, true); bt_->setTrackMuteState(5, true); } else if (prevTrack < 2) { bt_->setTrackMuteState(prevTrack, true); } else { bt_->setTrackMuteState(prevTrack + 3, true); } if (curTrack == 2) { bt_->setTrackMuteState(2, false); bt_->setTrackMuteState(3, false); bt_->setTrackMuteState(4, false); bt_->setTrackMuteState(5, false); } else if (curTrack < 2) { bt_->setTrackMuteState(curTrack, false); } else { bt_->setTrackMuteState(curTrack + 3, false); } } else { bt_->setTrackMuteState(prevTrack, true); bt_->setTrackMuteState(curTrack, false); } } try { QByteArray bytes; { const uint32_t rate = static_cast(dialog.getSampleRate()); const uint16_t nCh = 2; const int loopCnt = dialog.getLoopCount(); io::WavContainer container(rate, nCh, 16); if (!bt_->exportToWav(container, loopCnt, bar)) break; // Jump if cancelled bytes.reserve(container.size()); std::move(container.begin(), container.end(), std::back_inserter(bytes)); } if (curTrack > -1) path = QString("%1/%2 - %3.wav").arg(exDir).arg(curTrack + 1, 2, 10, QChar('0')).arg(name); QFile fp(path); if (!fp.open(QIODevice::WriteOnly)) { FileIOErrorMessageBox::openError(path, false, io::FileType::WAV, this); break; // Jump to post process } fp.write(bytes); fp.close(); bar(); config_.lock()->setWorkingDirectory(QFileInfo(path).dir().path().toStdString()); } catch (io::FileIOError& e) { FileIOErrorMessageBox(path, false, e, this).exec(); break; } catch (std::exception& e) { FileIOErrorMessageBox(path, false, io::FileType::WAV, QString(e.what()), this).exec(); break; } } // Restore states for (size_t i = 0 ; i < attribs.size(); ++i) { bt_->setTrackMuteState(attribs[i].number, muteStates[i]); } try { stream_->start(); } catch (std::exception& e) { showStreamFailedDialog(e.what()); } } void MainWindow::on_actionVGM_triggered() { VgmExportSettingsDialog dialog(this); if (dialog.exec() != QDialog::Accepted) return; io::GD3Tag tag = dialog.getGD3Tag(); QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory()); QString path = QFileDialog::getSaveFileName( this, tr("Export to VGM"), QString("%1/%2.vgm").arg(dir.isEmpty() ? "." : dir, getModuleFileBaseName()), tr("VGM file (*.vgm)") + ";;" + tr("All files (*)"), nullptr #if defined(Q_OS_LINUX) || (defined(Q_OS_BSD4) && !defined(Q_OS_DARWIN)) , QFileDialog::DontUseNativeDialog #endif ); if (path.isNull()) return; if (!path.endsWith(".vgm")) path += ".vgm"; // For linux int max = static_cast(bt_->getTotalStepCount(bt_->getCurrentSongNumber(), 1)) + 3; QProgressDialog progress(tr("Export to VGM"), tr("Cancel"), 0, max, this); progress.setValue(0); progress.setWindowFlags(progress.windowFlags() & ~Qt::WindowContextHelpButtonHint & ~Qt::WindowCloseButtonHint); progress.setWindowModality(Qt::ApplicationModal); progress.open(); bt_->stopPlaySong(); lockWidgets(false); try { stream_->stop(); } catch (std::exception& e) { showStreamFailedDialog(e.what()); } try { auto bar = [&progress]() -> bool { QApplication::processEvents(); progress.setValue(progress.value() + 1); return progress.wasCanceled(); }; QByteArray bytes; { io::BinaryContainer container; if (!bt_->exportToVgm(container, dialog.getExportTarget(), dialog.enabledGD3(), tag, dialog.isEnabledMix(), dialog.getGain(), bar)) goto AFTER_VGM_WRITE; // Jump if cancelled bytes.reserve(container.size()); std::move(container.begin(), container.end(), std::back_inserter(bytes)); } QFile fp(path); if (!fp.open(QIODevice::WriteOnly)) { FileIOErrorMessageBox::openError(path, false, io::FileType::VGM, this); goto AFTER_VGM_WRITE; // Jump if cancelled } fp.write(bytes); fp.close(); bar(); config_.lock()->setWorkingDirectory(QFileInfo(path).dir().path().toStdString()); } catch (io::FileIOError& e) { FileIOErrorMessageBox(path, false, e, this).exec(); } catch (std::exception& e) { FileIOErrorMessageBox(path, false, io::FileType::VGM, QString(e.what()), this).exec(); } AFTER_VGM_WRITE: try { stream_->start(); } catch (std::exception& e) { showStreamFailedDialog(e.what()); } } void MainWindow::on_actionS98_triggered() { S98ExportSettingsDialog dialog(this); if (dialog.exec() != QDialog::Accepted) return; io::S98Tag tag = dialog.getS98Tag(); QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory()); QString path = QFileDialog::getSaveFileName( this, tr("Export to S98"), QString("%1/%2.s98").arg(dir.isEmpty() ? "." : dir, getModuleFileBaseName()), tr("S98 file (*.s98)") + ";;" + tr("All files (*)"), nullptr #if defined(Q_OS_LINUX) || (defined(Q_OS_BSD4) && !defined(Q_OS_DARWIN)) , QFileDialog::DontUseNativeDialog #endif ); if (path.isNull()) return; if (!path.endsWith(".s98")) path += ".s98"; // For linux int max = static_cast(bt_->getTotalStepCount(bt_->getCurrentSongNumber(), 1)) + 3; QProgressDialog progress(tr("Export to S98"), tr("Cancel"), 0, max, this); progress.setValue(0); progress.setWindowFlags(progress.windowFlags() & ~Qt::WindowContextHelpButtonHint & ~Qt::WindowCloseButtonHint); progress.setWindowModality(Qt::ApplicationModal); progress.open(); bt_->stopPlaySong(); lockWidgets(false); try { stream_->stop(); } catch (std::exception& e) { showStreamFailedDialog(e.what()); } try { auto bar = [&progress]() -> bool { QApplication::processEvents(); progress.setValue(progress.value() + 1); return progress.wasCanceled(); }; QByteArray bytes; { io::BinaryContainer container; if (!bt_->exportToS98(container, dialog.getExportTarget(), dialog.enabledTag(), tag, dialog.getResolution(), bar)) goto AFTER_S98_WRITE; // Jump if cancelled bytes.reserve(container.size()); std::move(container.begin(), container.end(), std::back_inserter(bytes)); } QFile fp(path); if (!fp.open(QIODevice::WriteOnly)) { FileIOErrorMessageBox::openError(path, false, io::FileType::S98, this); goto AFTER_S98_WRITE; // Jump to post process } fp.write(bytes); fp.close(); bar(); config_.lock()->setWorkingDirectory(QFileInfo(path).dir().path().toStdString()); } catch (io::FileIOError& e) { FileIOErrorMessageBox(path, false, e, this).exec(); } catch (std::exception& e) { FileIOErrorMessageBox(path, false, io::FileType::S98, QString(e.what()), this).exec(); } AFTER_S98_WRITE: try { stream_->start(); } catch (std::exception& e) { showStreamFailedDialog(e.what()); } } void MainWindow::on_actionMix_triggered() { if (isEditedPattern_) ui->patternEditor->onPasteMixPressed(); } void MainWindow::on_actionOverwrite_triggered() { if (isEditedPattern_) ui->patternEditor->onPasteOverwritePressed(); } void MainWindow::onNewTickSignaledRealChip() { onNewTickSignaled(bt_->streamCountUp()); } void MainWindow::onNewTickSignaled(int state) { if (!state) { // New step int order = bt_->getPlayingOrderNumber(); if (order > -1) { // Playing if (isVisible() && !isMinimized()) { ui->orderList->updatePositionByOrderUpdate(firstViewUpdateRequest_); ui->patternEditor->updatePositionByStepUpdate(firstViewUpdateRequest_); firstViewUpdateRequest_ = false; } int width, base; if (config_.lock()->getShowRowNumberInHex()) { width = 2; base = 16; } else { width = 3; base = 10; } statusPlayPos_->setText( QString("%1/%2") .arg(order, width, base, QChar('0')) .arg(bt_->getPlayingStepNumber(), width, base, QChar('0')).toUpper()); } } else if (state == -1) { if (hasLockedWigets_) lockWidgets(false); } // Update BPM status if (bt_->getStreamGrooveEnabled()) { statusBpm_->setText(tr("Groove")); } else { // BPM = tempo * 6 / speed * 4 / 1st highlight double bpm = 24.0 * bt_->getStreamTempo() / bt_->getStreamSpeed() / highlight1_->value(); statusBpm_->setText(QString::number(bpm, 'f', 2) + QString(" BPM")); } } void MainWindow::on_actionClear_triggered() { fileHistory_->clearHistory(); for (int i = ui->menu_Recent_Files->actions().count() - 1; 1 < i; --i) ui->menu_Recent_Files->removeAction(ui->menu_Recent_Files->actions().at(i)); } void MainWindow::on_keyRepeatCheckBox_stateChanged(int arg1) { config_.lock()->setKeyRepetition(arg1 == Qt::Checked); } void MainWindow::updateVisuals() { int16_t wave[2 * bt_defs::OUTPUT_HISTORY_SIZE]; bt_->getOutputHistory(wave); ui->waveVisual->setStereoSamples(wave, bt_defs::OUTPUT_HISTORY_SIZE); } void MainWindow::on_action_Effect_List_triggered() { if (effListDialog_) { if (effListDialog_->isVisible()) effListDialog_->activateWindow(); else effListDialog_->show(); } else { effListDialog_ = std::make_unique(this); effListDialog_->show(); } } void MainWindow::on_actionShortcuts_triggered() { if (shortcutsDialog_) { if (shortcutsDialog_->isVisible()) shortcutsDialog_->activateWindow(); else shortcutsDialog_->show(); } else { shortcutsDialog_ = std::make_unique(this); shortcutsDialog_->show(); } } void MainWindow::on_actionExport_To_Bank_File_triggered() { exportInstrumentsToBank(); } void MainWindow::on_actionRemove_Duplicate_Instruments_triggered() { if (showUndoResetWarningDialog(tr("Do you want to remove all duplicate instruments?"))) { bt_->stopPlaySong(); lockWidgets(false); std::unordered_map rplMap = bt_->replaceDuplicateInstrumentsInPatterns(); auto list = ui->instrumentList; for (auto& pairs : rplMap) { for (int j = 0; j < list->count(); ++j) { if (list->item(j)->data(Qt::UserRole).toInt() == pairs.first) removeInstrument(j); } } bt_->clearUnusedInstrumentProperties(); bt_->clearCommandHistory(); comStack_->clear(); ui->patternEditor->onDuplicateInstrumentsRemoved(); setModifiedTrue(); } } void MainWindow::on_actionRename_Instrument_triggered() { renameInstrument(); } void MainWindow::on_action_Bookmark_Manager_triggered() { if (bmManForm_->isVisible()) bmManForm_->activateWindow(); else bmManForm_->show(); } void MainWindow::on_actionFine_Decrease_Values_triggered() { if (isEditedPattern_) ui->patternEditor->onChangeValuesPressed(false, false); } void MainWindow::on_actionFine_Increase_Values_triggered() { if (isEditedPattern_) ui->patternEditor->onChangeValuesPressed(false, true); } void MainWindow::on_actionCoarse_D_ecrease_Values_triggered() { if (isEditedPattern_) ui->patternEditor->onChangeValuesPressed(true, false); } void MainWindow::on_actionCoarse_I_ncrease_Values_triggered() { if (isEditedPattern_) ui->patternEditor->onChangeValuesPressed(true, true); } void MainWindow::on_action_Toggle_Bookmark_triggered() { bmManForm_->onBookmarkToggleRequested(bt_->getCurrentOrderNumber(), bt_->getCurrentStepNumber()); } void MainWindow::on_action_Next_Bookmark_triggered() { bmManForm_->onBookmarkJumpRequested(true, bt_->getCurrentOrderNumber(), bt_->getCurrentStepNumber()); } void MainWindow::on_action_Previous_Bookmark_triggered() { bmManForm_->onBookmarkJumpRequested(false, bt_->getCurrentOrderNumber(), bt_->getCurrentStepNumber()); } void MainWindow::on_action_Instrument_Mask_triggered() { config_.lock()->setInstrumentMask(ui->action_Instrument_Mask->isChecked()); } void MainWindow::on_action_Volume_Mask_triggered() { config_.lock()->setVolumeMask(ui->action_Volume_Mask->isChecked()); } void MainWindow::on_actionSet_Ro_w_Marker_triggered() { bt_->setMarker(bt_->getCurrentOrderNumber(), bt_->getCurrentStepNumber()); ui->patternEditor->changeMarker(); } void MainWindow::on_actionPlay_From_Marker_triggered() { startPlayFromMarker(); } void MainWindow::on_action_Go_To_triggered() { GoToDialog dialog(bt_, this); if (dialog.exec() == QDialog::Accepted) { if (!bt_->isPlaySong() || !bt_->isFollowPlay()) { bt_->setCurrentOrderNumber(dialog.getOrder()); bt_->setCurrentStepNumber(dialog.getStep()); bt_->setCurrentTrack(dialog.getTrack()); ui->orderList->updatePositionByPositionJump(true); ui->patternEditor->updatepositionByPositionJump(true); } } } void MainWindow::on_actionRemove_Unused_ADPCM_Samples_triggered() { if (showUndoResetWarningDialog(tr("Do you want to remove all unused ADPCM samples?"))) { bt_->stopPlaySong(); lockWidgets(false); bt_->clearUnusedADPCMSamples(); assignADPCMSamples(); bt_->clearCommandHistory(); comStack_->clear(); setModifiedTrue(); } } void MainWindow::on_action_Status_Bar_triggered() { ui->statusBar->setVisible(ui->action_Status_Bar->isChecked()); } void MainWindow::on_action_Toolbar_triggered() { bool visible = ui->action_Toolbar->isChecked(); ui->mainToolBar->setVisible(visible); ui->subToolBar->setVisible(visible); } void MainWindow::on_actionNew_Drumki_t_triggered() { addDrumkit(); } void MainWindow::on_action_Wave_View_triggered(bool checked) { config_.lock()->setVisibleWaveView(checked); ui->waveVisual->setVisible(checked); if (checked) visualTimer_->start(static_cast(std::round(1000. / config_.lock()->getWaveViewFrameRate()))); else visualTimer_->stop(); } void MainWindow::on_action_Transpose_Song_triggered() { TransposeSongDialog dialog(this); if (dialog.exec() == QDialog::Accepted) { if (showUndoResetWarningDialog(tr("Do you want to transpose a song?"))) { bt_->stopPlaySong(); lockWidgets(false); bt_->transposeSong(bt_->getCurrentSongNumber(), dialog.getTransposeSemitones(), dialog.getExcludeInstruments()); ui->patternEditor->onPatternDataGlobalChanged(); bt_->clearCommandHistory(); comStack_->clear(); setModifiedTrue(); } } } void MainWindow::on_action_Swap_Tracks_triggered() { SwapTracksDialog dialog(bt_->getSongStyle(bt_->getCurrentSongNumber()), this); if (dialog.exec() == QDialog::Accepted) { int track1 = dialog.getTrack1(); int track2 = dialog.getTrack2(); if ((track1 != track2) && showUndoResetWarningDialog(tr("Do you want to swap tracks?"))) { bt_->stopPlaySong(); lockWidgets(false); bt_->swapTracks(bt_->getCurrentSongNumber(), track1, track2); ui->orderList->onOrderDataGlobalChanged(); ui->patternEditor->onPatternDataGlobalChanged(); bt_->clearCommandHistory(); comStack_->clear(); setModifiedTrue(); } } } void MainWindow::on_action_Insert_triggered() { if (isEditedPattern_) ui->patternEditor->onPasteInsertPressed(); } void MainWindow::on_action_Hide_Tracks_triggered() { HideTracksDialog dialog(bt_->getSongStyle(bt_->getCurrentSongNumber()), ui->patternEditor->getVisibleTracks(), this); if (dialog.exec() == QDialog::Accepted) { std::vector visTracks = dialog.getVisibleTracks(); ui->orderList->setVisibleTracks(visTracks); setOrderListGroupMaximumWidth(); ui->patternEditor->setVisibleTracks(visTracks); int song = bt_->getCurrentSongNumber(); int all = static_cast(bt_->getSongStyle(song).trackAttribs.size()); for (int i = 0; i < all; ++i) { if (std::any_of(visTracks.begin(), visTracks.end(), [i](int n) { return i == n; })) { bt_->setTrackVisibility(song, i, true); } else { bt_->setTrackVisibility(song, i, false); if (config_.lock()->getMuteHiddenTracks()) { bt_->setTrackMuteState(i, true); } } } setModifiedTrue(); } } void MainWindow::on_action_Estimate_Song_Length_triggered() { double time = bt_->estimateSongLength(bt_->getCurrentSongNumber()); int seconds = static_cast(std::round(time)); QMessageBox dialog; dialog.setIcon(QMessageBox::Information); dialog.setText(tr("Approximate song length: %1m%2s") .arg(seconds / 60).arg(seconds % 60, 2, 10, QChar('0'))); dialog.exec(); } void MainWindow::on_action_Key_Signature_Manager_triggered() { if (ksManForm_->isVisible()) ksManForm_->activateWindow(); else ksManForm_->show(); } void MainWindow::on_action_Welcome_triggered() { welcomeDialog_->open(); } BambooTracker-0.6.5/BambooTracker/gui/mainwindow.hpp000066400000000000000000000303301476276175200224410ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef MAINWINDOW_HPP #define MAINWINDOW_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "enum_hash.hpp" #include "configuration.hpp" #include "bamboo_tracker.hpp" #include "precise_timer.hpp" #include "audio/audio_stream.hpp" #include "gui/instrument_editor/instrument_editor_manager.hpp" #include "gui/color_palette.hpp" #include "gui/file_history.hpp" #include "gui/effect_list_dialog.hpp" #include "gui/keyboard_shortcut_list_dialog.hpp" #include "gui/bookmark_manager_form.hpp" #include "gui/instrument_selection_dialog.hpp" #include "gui/comment_edit_dialog.hpp" #include "gui/key_signature_manager_form.hpp" namespace Ui { class ModuleSaveCheckDialog; class MainWindow; } class ModuleSaveCheckDialog : public QMessageBox { Q_OBJECT public: explicit ModuleSaveCheckDialog(const std::string& name, QWidget* parent = nullptr); }; class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(std::weak_ptr config, QString filePath, bool isFirstLaunch, QWidget *parent = nullptr); ~MainWindow() override; void assignADPCMSamples(); public slots: void onApplicationStateChanged(Qt::ApplicationState state); protected: bool eventFilter(QObject* watched, QEvent* event) override; void showEvent(QShowEvent*) override; void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; void dropEvent(QDropEvent* event) override; void resizeEvent(QResizeEvent* event) override; void moveEvent(QMoveEvent* event) override; void closeEvent(QCloseEvent* event) override; // Midi private: static void midiThreadReceivedEvent(double delay, const uint8_t *msg, size_t len, void *userData); private slots: void midiKeyEvent(uchar status, uchar key, uchar velocity); void midiProgramEvent(uchar status, uchar program); private: std::unique_ptr ui; std::weak_ptr config_; std::shared_ptr palette_; std::shared_ptr bt_; std::shared_ptr stream_; std::unique_ptr tickTimerForRealChip_; std::unique_ptr visualTimer_; std::shared_ptr comStack_; std::shared_ptr fileHistory_; std::shared_ptr scciDll_, c86ctlDll_; // Instrument list std::shared_ptr instDialogMan_; QListWidgetItem* renamingInstItem_; QLineEdit* renamingInstEdit_; void addInstrument(); void addDrumkit(); void removeInstrument(int row); void openInstrumentEditor(); int findRowFromInstrumentList(int instNum); void renameInstrument(); void finishRenamingInstrument(); void cloneInstrument(); void deepCloneInstrument(); void loadInstrument(); void funcLoadInstrument(QString file); void saveInstrument(); void importInstrumentsFromBank(); void funcImportInstrumentsFromBank(QString file); void exportInstrumentsToBank(); void swapInstruments(int row1, int row2); // Undo-Redo void undo(); void redo(); bool isModifiedForNotCommand_; // Load data void loadModule(); void openModule(const QString &file); void loadSong(); // Play song void startPlaySong(); void startPlayFromStart(); void startPlayPattern(); void startPlayFromCurrentStep(); void startPlayFromMarker(); void playStep(); void stopPlaySong(); bool hasLockedWigets_; void lockWidgets(bool isLock); // Octave change void changeOctave(bool upFlag); // Configuration change void changeConfiguration(); void setRealChipInterface(RealChipInterfaceType intf); void setMidiConfiguration(); void updateFonts(); // History change void changeFileHistory(QString file); // Backup bool backupModule(QString srcFile); void setWindowTitle(); void setModifiedTrue(); void setInitialSelectedInstrument(); QString getModuleFileBaseName() const; bool isEditedPattern_, isEditedOrder_, isEditedInstList_; bool isSelectedPattern_, isSelectedOrder_; bool hasShownOnce_; bool isSavedModBefore_; bool firstViewUpdateRequest_; // Menus std::unique_ptr pasteModeGroup_; // Toolbar QSpinBox *octave_, *highlight1_, *highlight2_, *volume_; // Status bars QLabel *statusStyle_, *statusInst_, *statusOctave_; QLabel *statusIntr_, *statusMixer_, *statusBpm_, *statusPlayPos_; // Shortcuts QAction octUpSc_, octDownSc_; QShortcut focusPtnSc_, focusOdrSc_, focusInstSc_; std::unique_ptr instAddSc_; QAction playAndStopSc_, playStepSc_, goPrevOdrSc_, goNextOdrSc_, prevInstSc_, nextInstSc_; QAction incPtnSizeSc_, decPtnSizeSc_, incEditStepSc_, decEditStepSc_, prevSongSc_, nextSongSc_; QAction jamVolUpSc_, jamVolDownSc_; void setShortcuts(); // Dialogs std::unique_ptr effListDialog_; std::unique_ptr shortcutsDialog_; std::unique_ptr bmManForm_; std::unique_ptr commentDialog_; std::unique_ptr ksManForm_; std::unique_ptr welcomeDialog_; // Bank import std::atomic_bool bankJamMidiCtrl_; std::unique_ptr importBankDialog_; // Meta methods int tickEventMethod_; int midiKeyEventMethod_; int midiProgramEventMethod_; void updateInstrumentListColors(); void setOrderListGroupMaximumWidth(); void freezeViews(); void unfreezeViews(); inline bool showUndoResetWarningDialog(QString text) { return (QMessageBox::warning(this, tr("Warning"), tr("%1 If you execute this command, the command history is reset.").arg(text), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes); } inline void showStreamFailedDialog(const QString& errDetail) { QMessageBox::critical(this, tr("Error"), tr("Could not open the audio stream. Please change the sound settings in Configuration.") + "\n\n" + errDetail, QMessageBox::Ok, QMessageBox::Ok); } inline void showStreamRateWarningDialog(uint32_t curRate) { QMessageBox::warning(this, tr("Warning"), tr("Could not set the sample rate of the audio stream to %1Hz. Currently the stream runs on %2Hz instead.") .arg(config_.lock()->getSampleRate()).arg(curRate), QMessageBox::Ok, QMessageBox::Ok); } inline void showMidiFailedDialog(const QString& errDetail) { QMessageBox::critical(this, tr("Error"), tr("Could not initialize MIDI input.") + "\n\n" + errDetail, QMessageBox::Ok, QMessageBox::Ok); } private slots: void on_instrumentList_customContextMenuRequested(const QPoint& pos); void on_instrumentList_itemDoubleClicked(QListWidgetItem*); void on_instrumentList_itemSelectionChanged(); void on_grooveCheckBox_stateChanged(int arg1); void on_actionExit_triggered(); void on_actionUndo_triggered(); void on_actionRedo_triggered(); void on_actionCut_triggered(); void on_actionCopy_triggered(); void on_actionPaste_triggered(); void on_actionDelete_triggered(); void updateMenuByPattern(); void updateMenuByOrder(); void onCurrentTrackChanged(); void updateMenuByInstrumentList(); void updateMenuByPatternSelection(bool isSelected); void updateMenuByOrderSelection(bool isSelected); void on_actionAll_triggered(); void on_actionNone_triggered(); void on_actionDecrease_Note_triggered(); void on_actionIncrease_Note_triggered(); void on_actionDecrease_Octave_triggered(); void on_actionIncrease_Octave_triggered(); void on_actionInsert_Order_triggered(); void on_actionRemove_Order_triggered(); void on_actionModule_Properties_triggered(); void on_actionNew_Instrument_triggered(); void on_actionRemove_Instrument_triggered(); void on_actionClone_Instrument_triggered(); void on_actionDeep_Clone_Instrument_triggered(); void on_actionEdit_triggered(); void on_actionPlay_triggered(); void on_actionPlay_Pattern_triggered(); void on_actionPlay_From_Start_triggered(); void on_actionPlay_From_Cursor_triggered(); void on_actionStop_triggered(); void on_actionEdit_Mode_triggered(); void on_actionToggle_Track_triggered(); void on_actionSolo_Track_triggered(); void on_actionKill_Sound_triggered(); void on_actionAbout_triggered(); void on_actionFollow_Mode_triggered(); void on_actionGroove_Settings_triggered(); void on_actionConfiguration_triggered(); void on_actionExpand_triggered(); void on_actionShrink_triggered(); void on_actionDuplicate_Order_triggered(); void on_actionMove_Order_Up_triggered(); void on_actionMove_Order_Down_triggered(); void on_actionClone_Patterns_triggered(); void on_actionClone_Order_triggered(); void on_actionNew_triggered(); void on_actionComments_triggered(); bool on_actionSave_triggered(); bool on_actionSave_As_triggered(); void on_actionOpen_triggered(); void on_actionLoad_From_File_triggered(); void on_actionSave_To_File_triggered(); void on_actionImport_From_Bank_File_triggered(); void on_actionInterpolate_triggered(); void on_actionReverse_triggered(); void on_actionReplace_Instrument_triggered(); void on_actionRow_triggered(); void on_actionColumn_triggered(); void on_actionPattern_triggered(); void on_actionOrder_triggered(); void on_actionRemove_Unused_Instruments_triggered(); void on_actionRemove_Unused_Patterns_triggered(); void on_actionWAV_triggered(); void on_actionVGM_triggered(); void on_actionS98_triggered(); void on_actionMix_triggered(); void on_actionOverwrite_triggered(); void onNewTickSignaledRealChip(); void onNewTickSignaled(int state); void on_actionClear_triggered(); void on_keyRepeatCheckBox_stateChanged(int arg1); void updateVisuals(); void on_action_Effect_List_triggered(); void on_actionShortcuts_triggered(); void on_actionExport_To_Bank_File_triggered(); void on_actionRemove_Duplicate_Instruments_triggered(); void on_actionRename_Instrument_triggered(); void on_action_Bookmark_Manager_triggered(); void on_actionFine_Decrease_Values_triggered(); void on_actionFine_Increase_Values_triggered(); void on_actionCoarse_D_ecrease_Values_triggered(); void on_actionCoarse_I_ncrease_Values_triggered(); void on_action_Toggle_Bookmark_triggered(); void on_action_Next_Bookmark_triggered(); void on_action_Previous_Bookmark_triggered(); void on_action_Instrument_Mask_triggered(); void on_action_Volume_Mask_triggered(); void on_actionSet_Ro_w_Marker_triggered(); void on_actionPlay_From_Marker_triggered(); void on_action_Go_To_triggered(); void on_actionRemove_Unused_ADPCM_Samples_triggered(); void on_action_Status_Bar_triggered(); void on_action_Toolbar_triggered(); void on_actionNew_Drumki_t_triggered(); void on_action_Wave_View_triggered(bool checked); void on_action_Transpose_Song_triggered(); void on_action_Swap_Tracks_triggered(); void on_action_Insert_triggered(); void on_action_Hide_Tracks_triggered(); void on_action_Estimate_Song_Length_triggered(); void on_action_Key_Signature_Manager_triggered(); void on_action_Welcome_triggered(); }; #endif // MAINWINDOW_HPP BambooTracker-0.6.5/BambooTracker/gui/mainwindow.ui000066400000000000000000001503531476276175200222770ustar00rootroot00000000000000 MainWindow 0 0 900 700 true BambooTracker 0 0 0 0 0 Qt::Vertical true 9 9 9 9 9 0 0 Order List 3 3 3 3 0 0 200 0 3 QLayout::SetDefaultConstraint 0 0 Song Settings 6 6 6 6 6 0 0 Tempo Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter false 32 255 150 0 0 Speed Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter false 1 31 6 0 0 Pattern size Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter false 1 256 64 0 0 Groove false false # 127 0 0 Edit settings 6 6 6 6 6 0 0 Step Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 0 0 false 0 256 0 0 Key repetition true 3 QLayout::SetDefaultConstraint 0 0 Module Settings 6 6 6 6 0 0 Title 0 0 Author 0 0 Copyright 0 0 180 45 Songs 6 6 6 6 0 0 0 0 Instruments 0 3 3 3 3 0 0 Qt::CustomContextMenu QFrame::Panel false true QAbstractItemView::DragDrop Qt::ActionMask 16 16 true QListView::Adjust 0 0 0 0 900 24 &File &Export &Recent Files &Edit &Select Paste Specia&l &Bookmarks Patter&n &Transpose &Change Values S&ong &Module Clean&up &Instrument &Tracker &Help Vie&w Main toolbar 16 16 TopToolBarArea false Secondary toolbar TopToolBarArea false true :/icon/new:/icon/new &New... Ctrl+N true :/icon/open:/icon/open &Open... Ctrl+O true :/icon/save:/icon/save &Save Ctrl+S true Save &As... E&xit false :/icon/undo:/icon/undo &Undo Ctrl+Z false :/icon/redo:/icon/redo &Redo Ctrl+Y :/icon/cut:/icon/cut Cu&t Ctrl+X :/icon/copy:/icon/copy &Copy Ctrl+C :/icon/paste:/icon/paste &Paste Ctrl+V &Delete Del &All Ctrl+A &None Esc false E&xpand false S&hrink &Decrease Note Ctrl+F1 &Increase Note Ctrl+F2 D&ecrease Octave Ctrl+F3 I&ncrease Octave Ctrl+F4 :/icon/insert_order:/icon/insert_order &Insert Order :/icon/remove_order:/icon/remove_order &Remove Order :/icon/property:/icon/property &Module Properties... Ctrl+P :/icon/add_inst:/icon/add_inst &New Instrument false :/icon/remove_inst:/icon/remove_inst &Remove Instrument false :/icon/clone_inst:/icon/clone_inst &Clone Instrument false &Deep Clone Instrument true :/icon/load_inst:/icon/load_inst &Load From File... false :/icon/save_inst:/icon/save_inst &Save To File... false :/icon/edit_inst:/icon/edit_inst &Edit... Ctrl+I :/icon/play:/icon/play &Play :/icon/play_pattern:/icon/play_pattern Play P&attern F6 Play &From Start F5 Play From C&ursor F7 :/icon/stop:/icon/stop &Stop F8 true :/icon/record:/icon/record &Edit Mode Space To&ggle Track Alt+F9 S&olo Track Alt+F10 &Kill Sound F12 &About... true true Fo&llow Mode ScrollLock &Groove Settings... :/icon/config:/icon/config &Configuration... :/icon/duplicate_order:/icon/duplicate_order &Duplicate Order Ctrl+D :/icon/order_up:/icon/order_up Move Order &Up :/icon/order_down:/icon/order_down Move Order Do&wn &Clone Patterns Alt+D Clone &Order &Comments... false &Interpolate Ctrl+G false &Reverse Ctrl+R false R&eplace Instrument Alt+S &Row &Column &Pattern &Order Remove Unused &Instruments Remove Unused &Patterns &WAV... &VGM... &Mix Ctrl+M &Overwrite &Import From Bank File... &S98... &Clear &Effect List... Effect List F1 &Shortcuts... false E&xport To Bank File... Remove &Duplicate Instruments false :/icon/rename_inst:/icon/rename_inst Re&name Instrument &Bookmark Manager... Fine &Decrease Values Shift+F1 Fine &Increase Values Shift+F2 Coarse D&ecrease Values Shift+F3 Coarse I&ncrease Values Shift+F4 false &Toggle Bookmark Ctrl+K &Next Bookmark Ctrl+PgDown &Previous Bookmark Ctrl+PgUp true &Instrument Mask true &Volume Mask Set Ro&w Marker Ctrl+B Play From &Marker Ctrl+F7 &Go To... Alt+G Remove Unused &ADPCM Samples true true &Status Bar true true &Toolbar false New Drumki&t true true &Wave View &Transpose Song... &Swap Tracks... &Insert true &Cursor true &Selection true &Fill &Hide Tracks... &Estimate Song Length... &Key Signature Manager... &Welcome... PatternEditor QFrame
gui/pattern_editor/pattern_editor.hpp
1
OrderListEditor QFrame
gui/order_list_editor/order_list_editor.hpp
1
WaveVisual QWidget
gui/wave_visual.hpp
1
DropDetectListWidget QListWidget
gui/drop_detect_list_widget.hpp
WheelSpinBox QSpinBox
gui/wheel_spin_box.hpp
tempoSpinBox speedSpinBox patternSizeSpinBox grooveCheckBox grooveSpinBox editableStepSpinBox keyRepeatCheckBox modTitleLineEdit authorLineEdit copyrightLineEdit songComboBox instrumentList
BambooTracker-0.6.5/BambooTracker/gui/module_properties_dialog.cpp000066400000000000000000000216761476276175200253550ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "module_properties_dialog.hpp" #include "ui_module_properties_dialog.h" #include #include #include #include #include "gui/gui_utils.hpp" #include "enum_hash.hpp" namespace { const std::unordered_map SONG_TYPE_TEXT = { { SongType::Standard, QT_TRANSLATE_NOOP("SongType", "Standard") }, { SongType::FM3chExpanded, QT_TRANSLATE_NOOP("SongType", "FM3ch expanded") } }; } ModulePropertiesDialog::ModulePropertiesDialog(std::weak_ptr core, double configFmMixer, double configSsgMixer, QWidget *parent) : QDialog(parent), ui(new Ui::ModulePropertiesDialog), bt_(core), configFmMixer_(configFmMixer), configSsgMixer_(configSsgMixer) { ui->setupUi(this); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); int tickFreq = static_cast(core.lock()->getModuleTickFrequency()); ui->customTickFreqSpinBox->setValue(tickFreq); switch (tickFreq) { case 60: ui->ntscRadioButton->setChecked(true); break; case 50: ui->palRadioButton->setChecked(true); break; default: ui->customTickFreqRadioButton->setChecked(true); break; } MixerType mixType = core.lock()->getModuleMixerType(); if (mixType == MixerType::UNSPECIFIED) { ui->mixerGroupBox->setChecked(false); } else { ui->mixerGroupBox->setChecked(true); ui->mixerTypeComboBox->setCurrentIndex(static_cast(mixType) - 1); } setCustomMixerLevels(core.lock()->getModuleCustomMixerFMLevel(), core.lock()->getModuleCustomMixerSSGLevel()); ui->songTreeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); int songCnt = static_cast(core.lock()->getSongCount()); for (int i = 0; i < songCnt; ++i) { auto title = core.lock()->getSongTitle(i); insertSong(i, gui_utils::utf8ToQString(title), core.lock()->getSongStyle(i).type, i); } ui->sngTypeComboBox->addItem(QCoreApplication::translate("SongType", SONG_TYPE_TEXT.at(SongType::Standard)), static_cast(SongType::Standard)); ui->sngTypeComboBox->addItem(QCoreApplication::translate("SongType", SONG_TYPE_TEXT.at(SongType::FM3chExpanded)), static_cast(SongType::FM3chExpanded)); } ModulePropertiesDialog::~ModulePropertiesDialog() { delete ui; } void ModulePropertiesDialog::insertSong(int row, QString title, SongType type, int prevNum) { QTreeWidgetItem* item = new QTreeWidgetItem(); item->setText(0, QString::number(row)); item->setData(0, Qt::UserRole, prevNum); item->setText(1, title); item->setText(2, SONG_TYPE_TEXT.at(type)); item->setData(2, Qt::UserRole, static_cast(type)); ui->songTreeWidget->insertTopLevelItem(row, item); for (int i = row + 1; i < ui->songTreeWidget->topLevelItemCount(); ++i) { ui->songTreeWidget->topLevelItem(i)->setText(0, QString::number(i)); } checkButtonsEnabled(); } void ModulePropertiesDialog::checkButtonsEnabled() { if (ui->songTreeWidget->currentItem() != nullptr && ui->songTreeWidget->topLevelItemCount() > 1) { ui->upToolButton->setEnabled(true); ui->downToolButton->setEnabled(true); ui->removePushButton->setEnabled(true); } else { ui->upToolButton->setEnabled(false); ui->downToolButton->setEnabled(false); ui->removePushButton->setEnabled(false); } } void ModulePropertiesDialog::swapset(int aboveRow, int belowRow) { auto* tree = ui->songTreeWidget; QTreeWidgetItem* below = tree->takeTopLevelItem(belowRow); if (tree->topLevelItemCount() > 2) { QTreeWidgetItem* above = tree->takeTopLevelItem(aboveRow); tree->insertTopLevelItem(aboveRow, below); tree->insertTopLevelItem(belowRow, above); } else { tree->insertTopLevelItem(aboveRow, below); } for (int i = aboveRow; i < ui->songTreeWidget->topLevelItemCount(); ++i) { ui->songTreeWidget->topLevelItem(i)->setText(0, QString::number(i)); } } void ModulePropertiesDialog::setCustomMixerLevels(double fm, double ssg) { fmMixer_ = fm; ssgMixer_ = ssg; ui->customMixerFMLevelLabel->setText(QString::asprintf("%+.1fdB", fmMixer_)); ui->customMixerSSGLevelLabel->setText(QString::asprintf("%+.1fdB", ssgMixer_)); } /******************************/ void ModulePropertiesDialog::on_upToolButton_clicked() { int curRow = ui->songTreeWidget->currentIndex().row(); if (!curRow) return; swapset(curRow - 1, curRow); ui->songTreeWidget->setCurrentItem(ui->songTreeWidget->topLevelItem(curRow - 1)); } void ModulePropertiesDialog::on_downToolButton_clicked() { int curRow = ui->songTreeWidget->currentIndex().row(); if (curRow == ui->songTreeWidget->topLevelItemCount() - 1) return; swapset(curRow, curRow + 1); ui->songTreeWidget->setCurrentItem(ui->songTreeWidget->topLevelItem(curRow + 1)); } void ModulePropertiesDialog::on_removePushButton_clicked() { int row = ui->songTreeWidget->currentIndex().row(); auto del = ui->songTreeWidget->takeTopLevelItem(row); delete del; for (int i = row; i < ui->songTreeWidget->topLevelItemCount(); ++ i) { ui->songTreeWidget->topLevelItem(i)->setText(0, QString::number(i)); } checkButtonsEnabled(); } void ModulePropertiesDialog::on_insertPushButton_clicked() { int row = ui->songTreeWidget->currentIndex().row(); if (row == -1) row = ui->songTreeWidget->topLevelItemCount(); insertSong(row, ui->sngTitleLineEdit->text(), static_cast(ui->sngTypeComboBox->currentData(Qt::UserRole).toInt())); ui->songTreeWidget->setCurrentItem(ui->songTreeWidget->topLevelItem(row)); } void ModulePropertiesDialog::on_songTreeWidget_itemSelectionChanged() { auto item = ui->songTreeWidget->currentItem(); ui->sngTitleLineEdit->setText(item->text(1)); int type = item->data(2, Qt::UserRole).toInt(); for (int i = 0; i < ui->sngTypeComboBox->count(); ++i) { if (ui->sngTypeComboBox->itemData(i, Qt::UserRole).toInt() == type) { ui->sngTypeComboBox->setCurrentIndex(i); break; } } checkButtonsEnabled(); } void ModulePropertiesDialog::onAccepted() { // Set tick frequency unsigned int tickFreq; if (ui->ntscRadioButton->isChecked()) tickFreq = 60; else if (ui->palRadioButton->isChecked()) tickFreq = 50; else tickFreq = static_cast(ui->customTickFreqSpinBox->value()); bt_.lock()->setModuleTickFrequency(tickFreq); // Set mixer if (ui->mixerGroupBox->isChecked()) { auto mixType = static_cast(ui->mixerTypeComboBox->currentIndex() + 1); bt_.lock()->setModuleMixerType(mixType); if (mixType == MixerType::CUSTOM) { bt_.lock()->setModuleCustomMixerFMLevel(fmMixer_); bt_.lock()->setModuleCustomMixerSSGLevel(ssgMixer_); } } else { bt_.lock()->setModuleMixerType(MixerType::UNSPECIFIED); } auto* tree = ui->songTreeWidget; std::vector newSongNums; for (int i = 0; i < tree->topLevelItemCount(); ++i) { QTreeWidgetItem* item = tree->topLevelItem(i); SongType type = static_cast(item->data(2, Qt::UserRole).toInt()); std::string title = item->text(1).toUtf8().toStdString(); if (item->data(0, Qt::UserRole).toInt() == -1) { // Add new song int n = static_cast(bt_.lock()->getSongCount()); bt_.lock()->addSong(type, title); newSongNums.push_back(n); } else { // Update song data int n = item->data(0, Qt::UserRole).toInt(); bt_.lock()->setSongTitle(n, title); if (bt_.lock()->getSongStyle(n).type != type) bt_.lock()->changeSongType(n, type); newSongNums.push_back(n); } } // Sort songs bt_.lock()->sortSongs(std::move(newSongNums)); } void ModulePropertiesDialog::on_mixerTypeComboBox_currentIndexChanged(int index) { ui->mixerCustomGroupBox->setEnabled(index == 0); } void ModulePropertiesDialog::on_customMixerSetPushButton_clicked() { setCustomMixerLevels(configFmMixer_, configSsgMixer_); } void ModulePropertiesDialog::on_updateButton_clicked() { if (auto item = ui->songTreeWidget->currentItem()) { item->setText(1, ui->sngTitleLineEdit->text()); auto typeInt = ui->sngTypeComboBox->currentData(Qt::UserRole).toInt(); item->setText(2, SONG_TYPE_TEXT.at(static_cast(typeInt))); item->setData(2, Qt::UserRole, typeInt); } } BambooTracker-0.6.5/BambooTracker/gui/module_properties_dialog.hpp000066400000000000000000000045131476276175200253510ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef MODULE_PROPERTIES_DIALOG_HPP #define MODULE_PROPERTIES_DIALOG_HPP #include #include #include #include #include "bamboo_tracker.hpp" namespace Ui { class ModulePropertiesDialog; } class ModulePropertiesDialog : public QDialog { Q_OBJECT public: ModulePropertiesDialog(std::weak_ptr core, double configFmMixer, double configSsgMixer, QWidget *parent = nullptr); ~ModulePropertiesDialog() override; public slots: void onAccepted(); private slots: void on_upToolButton_clicked(); void on_downToolButton_clicked(); void on_removePushButton_clicked(); void on_insertPushButton_clicked(); void on_songTreeWidget_itemSelectionChanged(); void on_mixerTypeComboBox_currentIndexChanged(int index); void on_customMixerSetPushButton_clicked(); void on_updateButton_clicked(); private: Ui::ModulePropertiesDialog *ui; std::weak_ptr bt_; double fmMixer_, ssgMixer_; double configFmMixer_, configSsgMixer_; void insertSong(int row, QString title, SongType type, int prevNum = -1); void checkButtonsEnabled(); void swapset(int aboveRow, int belowRow); void setCustomMixerLevels(double fm, double ssg); }; #endif // MODULE_PROPERTIES_DIALOG_HPP BambooTracker-0.6.5/BambooTracker/gui/module_properties_dialog.ui000066400000000000000000000307511476276175200252020ustar00rootroot00000000000000 ModulePropertiesDialog 0 0 417 491 Module properties Tick frequency 60Hz (NTSC) true tickFreqButtonGroup 50Hz (PAL) tickFreqButtonGroup 0 Custom tickFreqButtonGroup Hz 1 511 60 Mixer true true 1 Custom PC-9821 with PC-9801-86 PC-9821 with Speak Board PC-88VA2 PC-8801mkIISR false Custom mixer FM Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter QFrame::WinPanel QFrame::Sunken +0.0dB Qt::AlignCenter SSG Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter QFrame::WinPanel QFrame::Sunken +0.0dB Qt::AlignCenter Set Song control Song Title Song type true Qt::Horizontal 40 20 0 0 Insert Update Untitled false 0 0 Qt::DownArrow false 3 Number Title Song type false 0 0 Qt::UpArrow Qt::Vertical 20 40 false Remove Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok ntscRadioButton palRadioButton customTickFreqRadioButton customTickFreqSpinBox mixerGroupBox mixerTypeComboBox customMixerSetPushButton songTreeWidget upToolButton downToolButton removePushButton sngTitleLineEdit sngTypeComboBox insertPushButton updateButton buttonBox accepted() ModulePropertiesDialog accept() 248 254 157 274 buttonBox rejected() ModulePropertiesDialog reject() 316 260 286 274 BambooTracker-0.6.5/BambooTracker/gui/note_name_manager.cpp000066400000000000000000000164231476276175200237260ustar00rootroot00000000000000/* * Copyright (C) 2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "note_name_manager.hpp" #include #include "configuration.hpp" #include "enum_hash.hpp" namespace { const std::unordered_map NAMES_EN = { { KeySignature::C, { "C", "Db", "D", "Eb", "E", "F", "F#", "G", "Ab", "A", "Bb", "B" } }, { KeySignature::CS, { "B#", "C#", "D", "D#", "E", "E#", "F#", "Fx", "G#", "A", "A#", "B" } }, { KeySignature::DF, { "C", "Db", "EB", "Eb", "Fb", "F", "Gb", "G", "Ab", "BB", "Bb", "Cb" } }, { KeySignature::D, { "C", "C#", "D", "Eb", "E", "F", "F#", "G", "G#", "A", "Bb", "B" } }, { KeySignature::DS, { "B#", "C#", "Cx", "D#", "E", "E#", "F#", "Fx", "G#", "Gx", "A#", "B" } }, { KeySignature::EF, { "C", "Db", "D", "Eb", "Fb", "F", "Gb", "G", "Ab", "A", "Bb", "Cb" } }, { KeySignature::E, { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" } }, { KeySignature::FF, { "DB", "Db", "EB", "Eb", "Fb", "GB", "Gb", "AB", "Ab", "BB", "Bb", "Cb" } }, { KeySignature::ES, { "B#", "C#", "Cx", "D#", "Dx", "E#", "F#", "Fx", "G#", "Gx", "A#", "Ax" } }, { KeySignature::F, { "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B" } }, { KeySignature::FS, { "B#", "C#", "D", "D#", "E", "E#", "F#", "G", "G#", "A", "A#", "B" } }, { KeySignature::GF, { "C", "Db", "EB", "Eb", "Fb", "F", "Gb", "AB", "Ab", "BB", "Bb", "Cb" } }, { KeySignature::G, { "C", "C#", "D", "Eb", "E", "F", "F#", "G", "Ab", "A", "Bb", "B" } }, { KeySignature::GS, { "B#", "C#", "Cx", "D#", "E", "E#", "F#", "Fx", "G#", "A", "A#", "B" } }, { KeySignature::AF, { "C", "Db", "D", "Eb", "Fb", "F", "Gb", "G", "Ab", "BB", "Bb", "Cb" } }, { KeySignature::A, { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "Bb", "B" } }, { KeySignature::AS, { "B#", "C#", "Cx", "D#", "Dx", "E#", "F#", "Fx", "G#", "Gx", "A#", "B" } }, { KeySignature::BF, { "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "Cb" } }, { KeySignature::B, { "C", "C#", "D", "D#", "E", "E#", "F#", "G", "G#", "A", "A#", "B" } }, { KeySignature::CF, { "DB", "Db", "EB", "Eb", "Fb", "F", "Gb", "AB", "Ab", "BB", "Bb", "Cb" } }, { KeySignature::BS, { "B#", "C#", "Cx", "D#", "Dx", "E#", "Ex", "Fx", "G#", "Gx", "A#", "Ax" } } }; const std::unordered_map NAMES_DE = { { KeySignature::C, { "C", "Db", "D", "Eb", "E", "F", "F#", "G", "Ab", "A", "B", "H" } }, { KeySignature::CS, { "H#", "C#", "D", "D#", "E", "E#", "F#", "Fx", "G#", "A", "A#", "H" } }, { KeySignature::DF, { "C", "Db", "EB", "Eb", "Fb", "F", "Gb", "G", "Ab", "HB", "B", "Cb" } }, { KeySignature::D, { "C", "C#", "D", "Eb", "E", "F", "F#", "G", "G#", "A", "B", "H" } }, { KeySignature::DS, { "H#", "C#", "Cx", "D#", "E", "E#", "F#", "Fx", "G#", "Gx", "A#", "H" } }, { KeySignature::EF, { "C", "Db", "D", "Eb", "Fb", "F", "Gb", "G", "Ab", "A", "B", "Cb" } }, { KeySignature::E, { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "H" } }, { KeySignature::FF, { "DB", "Db", "EB", "Eb", "Fb", "GB", "Gb", "AB", "Ab", "HB", "B", "Cb" } }, { KeySignature::ES, { "H#", "C#", "Cx", "D#", "Dx", "E#", "F#", "Fx", "G#", "Gx", "A#", "Ax" } }, { KeySignature::F, { "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "B", "H" } }, { KeySignature::FS, { "H#", "C#", "D", "D#", "E", "E#", "F#", "G", "G#", "A", "A#", "H" } }, { KeySignature::GF, { "C", "Db", "EB", "Eb", "Fb", "F", "Gb", "AB", "Ab", "HB", "B", "Cb" } }, { KeySignature::G, { "C", "C#", "D", "Eb", "E", "F", "F#", "G", "Ab", "A", "B", "H" } }, { KeySignature::GS, { "H#", "C#", "Cx", "D#", "E", "E#", "F#", "Fx", "G#", "A", "A#", "H" } }, { KeySignature::AF, { "C", "Db", "D", "Eb", "Fb", "F", "Gb", "G", "Ab", "HB", "B", "Cb" } }, { KeySignature::A, { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "B", "H" } }, { KeySignature::AS, { "H#", "C#", "Cx", "D#", "Dx", "E#", "F#", "Fx", "G#", "Gx", "A#", "H" } }, { KeySignature::BF, { "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "B", "Cb" } }, { KeySignature::B, { "C", "C#", "D", "D#", "E", "E#", "F#", "G", "G#", "A", "A#", "H" } }, { KeySignature::CF, { "DB", "Db", "EB", "Eb", "Fb", "F", "Gb", "AB", "Ab", "HB", "B", "Cb" } }, { KeySignature::BS, { "H#", "C#", "Cx", "D#", "Dx", "E#", "Ex", "Fx", "G#", "Gx", "A#", "Ax" } } }; std::unordered_map> NAMES = { { NoteNotationSystem::ENGLISH, NAMES_EN }, { NoteNotationSystem::GERMAN, NAMES_DE } }; std::unordered_map KEYS = { { KeySignature::C, 0 }, { KeySignature::BS, 0 }, { KeySignature::CS, 1 }, { KeySignature::DF, 1 }, { KeySignature::D, 2 }, { KeySignature::DS, 3 }, { KeySignature::EF, 3 }, { KeySignature::E, 4 }, { KeySignature::FF, 4 }, { KeySignature::ES, 5 }, { KeySignature::F, 5 }, { KeySignature::FS, 6 }, { KeySignature::GF, 6 }, { KeySignature::G, 7 }, { KeySignature::GS, 8 }, { KeySignature::AF, 8 }, { KeySignature::A, 9 }, { KeySignature::AS, 10 }, { KeySignature::BF, 10 }, { KeySignature::B, 11 }, { KeySignature::CF, 11 } }; } std::unique_ptr NoteNameManager::instance_; NoteNameManager& NoteNameManager::getManager() { if (instance_) return *instance_; NoteNameManager *out = new NoteNameManager; instance_.reset(out); return *out; } NoteNameManager::NoteNameManager() : list_(&NAMES.at(NoteNotationSystem::ENGLISH)) {} NoteNameManager::~NoteNameManager() = default; void NoteNameManager::setNotationSystem(NoteNotationSystem system) { list_ = &NAMES.at(system); } QString NoteNameManager::getNoteName(int n, KeySignature::Type key) const { return list_->at(key).at(n); } QString NoteNameManager::getNoteString(int noteNum, KeySignature::Type key) const { return QString("%1%2").arg(list_->at(key).at(noteNum % 12), -2, QChar('-')).arg(noteNum / 12); } QString NoteNameManager::getKeyName(KeySignature::Type key) const { return list_->at(key).at(KEYS.at(key)); } BambooTracker-0.6.5/BambooTracker/gui/note_name_manager.hpp000066400000000000000000000034761476276175200237370ustar00rootroot00000000000000/* * Copyright (C) 2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef NOTE_NAME_MANAGER_HPP #define NOTE_NAME_MANAGER_HPP #include #include #include #include "song.hpp" enum class NoteNotationSystem : int; class NoteNameManager final { public: static NoteNameManager& getManager(); ~NoteNameManager(); void setNotationSystem(NoteNotationSystem system); QString getNoteName(int n, KeySignature::Type key = KeySignature::E) const; QString getNoteString(int noteNum, KeySignature::Type key = KeySignature::E) const; QString getKeyName(KeySignature::Type key) const; private: static std::unique_ptr instance_; NoteNameManager(); const std::unordered_map* list_; }; #endif // NOTE_NAME_MANAGER_HPP BambooTracker-0.6.5/BambooTracker/gui/order_list_editor/000077500000000000000000000000001476276175200232715ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/gui/order_list_editor/order_list_editor.cpp000066400000000000000000000206251476276175200275160ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "order_list_editor.hpp" #include "ui_order_list_editor.h" OrderListEditor::OrderListEditor(QWidget *parent) : QFrame(parent), ui(new Ui::OrderListEditor), freezed_(false), songLoaded_(false), hScrollCellMove_(true) { ui->setupUi(this); installEventFilter(this); ui->panel->installEventFilter(this); ui->verticalScrollBar->installEventFilter(this); QObject::connect(ui->panel, &OrderListPanel::hScrollBarChangeRequested, ui->horizontalScrollBar, &QScrollBar::setValue); QObject::connect(ui->panel, &OrderListPanel::vScrollBarChangeRequested, this, [&](int num, int max) { if (ui->verticalScrollBar->maximum() < num) { ui->verticalScrollBar->setMaximum(max); ui->verticalScrollBar->setValue(num); } else { ui->verticalScrollBar->setValue(num); ui->verticalScrollBar->setMaximum(max); } }); QObject::connect(ui->panel, &OrderListPanel::currentTrackChanged, this, [&](int idx) { emit currentTrackChanged(idx); }); QObject::connect(ui->panel, &OrderListPanel::currentOrderChanged, this, [&](int num) { emit currentOrderChanged(num); }); QObject::connect(ui->panel, &OrderListPanel::orderEdited, this, [&] { emit orderEdited(); }); QObject::connect(ui->panel, &OrderListPanel::selected, this, [&](bool isSelected) { emit selected(isSelected); }); auto focusSlot = [&] { ui->panel->setFocus(); }; QObject::connect(ui->horizontalScrollBar, &QScrollBar::valueChanged, ui->panel, &OrderListPanel::onHScrollBarChanged); QObject::connect(ui->horizontalScrollBar, &QScrollBar::sliderPressed, this, focusSlot); QObject::connect(ui->verticalScrollBar, &QScrollBar::valueChanged, ui->panel, &OrderListPanel::onVScrollBarChanged); QObject::connect(ui->verticalScrollBar, &QScrollBar::sliderPressed, this, focusSlot); } OrderListEditor::~OrderListEditor() { delete ui; } void OrderListEditor::setCore(std::shared_ptr core) { bt_ = core; ui->panel->setCore(core); } void OrderListEditor::setCommandStack(std::weak_ptr stack) { ui->panel->setCommandStack(stack); } void OrderListEditor::setConfiguration(std::shared_ptr config) { ui->panel->setConfiguration(config); } void OrderListEditor::setColorPallete(std::shared_ptr palette) { ui->panel->setColorPallete(palette); } void OrderListEditor::addActionToPanel(QAction* action) { ui->panel->addAction(action); } void OrderListEditor::changeEditable() { ui->panel->changeEditable(); } void OrderListEditor::updatePositionByOrderUpdate(bool isFirstUpdate) { ui->panel->updatePositionByOrderUpdate(isFirstUpdate); } void OrderListEditor::updatePositionByPositionJump(bool trackChanged) { ui->panel->updatePositionByOrderUpdate(false, true, trackChanged); } void OrderListEditor::copySelectedCells() { ui->panel->copySelectedCells(); } void OrderListEditor::deleteOrder() { ui->panel->deleteOrder(); } void OrderListEditor::insertOrderBelow() { ui->panel->insertOrderBelow(); } void OrderListEditor::freeze() { setUpdatesEnabled(false); freezed_ = true; ui->panel->waitPaintFinish(); } void OrderListEditor::unfreeze() { freezed_ = false; setUpdatesEnabled(true); } QFont OrderListEditor::getHeaderFont() const { return ui->panel->getHeaderFont(); } QFont OrderListEditor::getRowsFont() const { return ui->panel->getRowsFont(); } QFont OrderListEditor::getDefaultHeaderFont() const { return ui->panel->getDefaultHeaderFont(); } QFont OrderListEditor::getDefaultRowsFont() const { return ui->panel->getDefaultRowsFont(); } void OrderListEditor::setFonts(const QFont& headerFont, const QFont& rowsFont) { ui->panel->setFonts(headerFont, rowsFont); } void OrderListEditor::setHorizontalScrollMode(bool cellBased, bool refresh) { hScrollCellMove_ = cellBased; if (refresh) updateHorizontalSliderMaximum(); } void OrderListEditor::setVisibleTracks(std::vector tracks) { ui->horizontalScrollBar->setMaximum(20); // Dummy ui->panel->setVisibleTracks(tracks); updateMaximumWidth(); updateHorizontalSliderMaximum(); } bool OrderListEditor::eventFilter(QObject* watched, QEvent* event) { if (freezed_) return true; // Ignore every events if (watched == this) { if (event->type() == QEvent::FocusIn) { ui->panel->setFocus(); } return false; } if (watched == ui->panel) { switch (event->type()) { case QEvent::FocusIn: ui->panel->redrawByFocusChanged(); emit focusIn(); return false; case QEvent::FocusOut: ui->panel->redrawByFocusChanged(); emit focusOut(); return false; case QEvent::HoverEnter: case QEvent::HoverLeave: ui->panel->redrawByHoverChanged(); return false; default: return false; } } if (watched == ui->verticalScrollBar) { switch (event->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: case QEvent::DragMove: case QEvent::Wheel: return (bt_->isPlaySong() && bt_->isFollowPlay()); default: return false; } } return false; } void OrderListEditor::resizeEvent(QResizeEvent*) { // For view-based scroll updateHorizontalSliderMaximum(); } /********** Slots **********/ void OrderListEditor::onPatternEditorCurrentTrackChanged(int idx) { ui->panel->onPatternEditorCurrentTrackChanged(idx); } void OrderListEditor::onPatternEditorCurrentOrderChanged(int num, int max) { ui->panel->onPatternEditorCurrentOrderChanged(num); ui->verticalScrollBar->setMaximum(max); ui->verticalScrollBar->setValue(num); } void OrderListEditor::onSongLoaded() { ui->horizontalScrollBar->setValue(0); ui->panel->onSongLoaded(); updateMaximumWidth(); int song = bt_->getCurrentSongNumber(); songLoaded_ = true; updateHorizontalSliderMaximum(); ui->verticalScrollBar->setValue(0); // Left here to set appropriate order size before initialization of order position ui->verticalScrollBar->setMaximum(static_cast(bt_->getOrderSize(song)) - 1); } void OrderListEditor::onShortcutUpdated() { ui->panel->onShortcutUpdated(); } void OrderListEditor::onPastePressed() { ui->panel->onPastePressed(); } void OrderListEditor::onSelectPressed(int type) { ui->panel->onSelectPressed(type); } void OrderListEditor::onDuplicatePressed() { ui->panel->onDuplicatePressed(); } void OrderListEditor::onMoveOrderPressed(bool isUp) { ui->panel->onMoveOrderPressed(isUp); } void OrderListEditor::onClonePatternsPressed() { ui->panel->onClonePatternsPressed(); } void OrderListEditor::onCloneOrderPressed() { ui->panel->onCloneOrderPressed(); } void OrderListEditor::onFollowModeChanged() { ui->panel->onFollowModeChanged(); } void OrderListEditor::onStoppedPlaySong() { ui->panel->onStoppedPlaySong(); } void OrderListEditor::onGoOrderRequested(bool toNext) { ui->panel->onGoOrderRequested(toNext); } void OrderListEditor::onOrderDataGlobalChanged() { ui->panel->redrawByPatternChanged(); // Redraw only text } void OrderListEditor::updateHorizontalSliderMaximum() { if (!bt_ || !songLoaded_) return; int max = hScrollCellMove_ ? ui->panel->getFullColumnSize() : ui->panel->getScrollableCountByTrack(); ui->horizontalScrollBar->setMaximum(max); } void OrderListEditor::updateMaximumWidth() { int w; if (ui->horizontalScrollBar->sizeHint().width() < ui->panel->maximumWidth()) { w = ui->panel->maximumWidth(); } else { w = ui->horizontalScrollBar->sizeHint().width(); ui->panel->setMaximumWidth(w); } setMaximumWidth(w + ui->verticalScrollBar->width() + 2); } BambooTracker-0.6.5/BambooTracker/gui/order_list_editor/order_list_editor.hpp000066400000000000000000000066271476276175200275310ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef ORDER_LIST_EDITOR_HPP #define ORDER_LIST_EDITOR_HPP #include #include #include #include #include "bamboo_tracker.hpp" #include "configuration.hpp" #include "gui/color_palette.hpp" namespace Ui { class OrderListEditor; } class OrderListEditor : public QFrame { Q_OBJECT public: explicit OrderListEditor(QWidget *parent = nullptr); ~OrderListEditor() override; void setCore(std::shared_ptr core); void setCommandStack(std::weak_ptr stack); void setConfiguration(std::shared_ptr config); void setColorPallete(std::shared_ptr palette); void addActionToPanel(QAction* action); void changeEditable(); void updatePositionByOrderUpdate(bool isFirstUpdate); void updatePositionByPositionJump(bool trackChanged = false); void copySelectedCells(); void deleteOrder(); void insertOrderBelow(); void freeze(); void unfreeze(); QFont getHeaderFont() const; QFont getRowsFont() const; QFont getDefaultHeaderFont() const; QFont getDefaultRowsFont() const; void setFonts(const QFont& headerFont, const QFont& rowsFont); void setHorizontalScrollMode(bool cellBased, bool refresh = true); void setVisibleTracks(std::vector tracks); signals: void currentTrackChanged(int idx); void currentOrderChanged(int num); void orderEdited(); void focusIn(); void focusOut(); void selected(bool isSelected); public slots: void onPatternEditorCurrentTrackChanged(int idx); void onPatternEditorCurrentOrderChanged(int num, int max); void onSongLoaded(); void onShortcutUpdated(); void onPastePressed(); /// 0: None /// 1: All /// 2: Row /// 3: Column /// 4: Pattern /// 5: Order void onSelectPressed(int type); void onDuplicatePressed(); void onMoveOrderPressed(bool isUp); void onClonePatternsPressed(); void onCloneOrderPressed(); void onFollowModeChanged(); void onStoppedPlaySong(); void onGoOrderRequested(bool toNext); void onOrderDataGlobalChanged(); protected: bool eventFilter(QObject* watched, QEvent* event) override; void resizeEvent(QResizeEvent*) override; private: Ui::OrderListEditor *ui; std::shared_ptr bt_; bool freezed_; bool songLoaded_; bool hScrollCellMove_; void updateHorizontalSliderMaximum(); void updateMaximumWidth(); }; #endif // ORDER_LIST_EDITOR_HPP BambooTracker-0.6.5/BambooTracker/gui/order_list_editor/order_list_editor.ui000066400000000000000000000050271476276175200273500ustar00rootroot00000000000000 OrderListEditor 0 0 400 300 0 0 Frame QFrame::Panel QFrame::Sunken 1 QLayout::SetMinimumSize 0 0 0 0 0 Qt::Horizontal 0 0 17 0 17 16777215 Qt::Vertical OrderListPanel QWidget
gui/order_list_editor/order_list_panel.hpp
1
BambooTracker-0.6.5/BambooTracker/gui/order_list_editor/order_list_panel.cpp000066400000000000000000001400361476276175200273260ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "order_list_panel.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include #endif #include #include #include #include "playback.hpp" #include "track.hpp" #include "bamboo_tracker_defs.hpp" #include "gui/dpi.hpp" #include "gui/event_guard.hpp" #include "gui/command/order/order_commands_qt.hpp" #include "gui/command_result_message_box.hpp" #include "gui/gui_utils.hpp" #include "vector_2d.hpp" #include "utils.hpp" using Dpi::scaledQPixmap; using Dpi::iRatio; using Dpi::scaleRect; OrderListPanel::OrderListPanel(QWidget *parent) : QWidget(parent), config_(std::make_shared()), // Dummy rowFontWidth_(0), rowFontHeight_(0), rowFontAscent_(0), rowFontLeading_(0), headerFontAscent_(0), widthSpace_(0), rowNumWidthCnt_(0), rowNumWidth_(0), rowNumBase_(0), trackWidth_(0), columnsWidthFromLeftToEnd_(0), headerHeight_(0), curRowBaselineY_(0), curRowY_(0), visTracks_(1), // Dummy leftTrackVisIdx_(0), curSongNum_(0), curPos_{ 0, 0 }, hovPos_{ -1, -1 }, mousePressPos_{ -1, -1 }, mouseReleasePos_{ -1, -1 }, selLeftAbovePos_{ -1, -1 }, selRightBelowPos_{ -1, -1 }, shiftPressedPos_{ -1, -1 }, isIgnoreToSlider_(false), isIgnoreToPattern_(false), entryCnt_(0), selectAllState_(-1), viewedRowCnt_(1), viewedRowsHeight_(0), viewedRowOffset_(0), viewedCenterY_(0), viewedCenterBaseY_(0), backChanged_(false), textChanged_(false), headerChanged_(false), followModeChanged_(false), hasFocussedBefore_(false), orderDownCount_(0), repaintable_(true), repaintingCnt_(0), playingRow_(-1), insSc1_(Qt::Key_Insert, this, nullptr, nullptr, Qt::WidgetShortcut), insSc2_(Qt::ALT | Qt::Key_B, this, nullptr, nullptr, Qt::WidgetShortcut), menuSc_(Qt::Key_Menu, this, nullptr, nullptr, Qt::WidgetShortcut) { setAttribute(Qt::WA_Hover); setFocusPolicy(Qt::ClickFocus); setContextMenuPolicy(Qt::CustomContextMenu); // Initialize font headerFontDef_ = [] { auto font = QApplication::font(); font.setPointSize(10); return font; }(); headerFont_ = headerFontDef_; rowFontDef_ = [] { QFont font("Monospace", 10); font.setStyleHint(QFont::TypeWriter); font.setStyleStrategy(QFont::PreferMatch); // Get actually used font QFontInfo info(font); return QFont(info.family(), info.pointSize()); }(); rowFont_ = rowFontDef_; updateSizes(); // Track visibility songStyle_.type = SongType::Standard; // Dummy songStyle_.trackAttribs.push_back({ 0, SoundSource::FM, 0 }); // Dummy std::iota(visTracks_.begin(), visTracks_.end(), 0); // Shortcuts QObject::connect(&insSc1_, &QShortcut::activated, this, &OrderListPanel::insertOrderBelow); QObject::connect(&insSc2_, &QShortcut::activated, this, &OrderListPanel::insertOrderBelow); QObject::connect(&menuSc_, &QShortcut::activated, this, [&] { showContextMenu( curPos_, QPoint(calculateColumnsWidthWithRowNum(leftTrackVisIdx_, curPos_.trackVisIdx), curRowY_ - 8)); }); onShortcutUpdated(); } void OrderListPanel::setCore(std::shared_ptr core) { bt_ = core; } void OrderListPanel::setCommandStack(std::weak_ptr stack) { comStack_ = stack; } void OrderListPanel::setConfiguration(std::shared_ptr config) { config_ = config; } void OrderListPanel::setColorPallete(std::shared_ptr palette) { palette_ = palette; } void OrderListPanel::resetEntryCount() { entryCnt_ = 0; } void OrderListPanel::waitPaintFinish() { while (true) { if (repaintingCnt_.load()) std::this_thread::sleep_for(std::chrono::milliseconds(10)); else { curPos_.row = 0; // Init return; } } } QFont OrderListPanel::getHeaderFont() const { return headerFont_; } QFont OrderListPanel::getRowsFont() const { return rowFont_; } QFont OrderListPanel::getDefaultHeaderFont() const { return headerFontDef_; } QFont OrderListPanel::getDefaultRowsFont() const { return rowFontDef_; } void OrderListPanel::setFonts(const QFont& headerFont, const QFont& rowsFont) { headerFont_ = headerFont; rowFont_ = rowsFont; updateSizes(); updateTracksWidthFromLeftToEnd(); setMaximumWidth(calculateColumnsWidthWithRowNum( 0, static_cast(visTracks_.size()) - 1)); redrawAll(); } void OrderListPanel::setVisibleTracks(std::vector tracks) { visTracks_ = tracks; int max = static_cast(tracks.size()); bool cond = (max <= curPos_.trackVisIdx); if (cond) curPos_.trackVisIdx = max; leftTrackVisIdx_ = std::min(leftTrackVisIdx_, curPos_.trackVisIdx); updateTracksWidthFromLeftToEnd(); setMaximumWidth(calculateColumnsWidthWithRowNum( 0, static_cast(visTracks_.size()) - 1)); initDisplay(); // Current track in core is changed in the pattern editor if (cond) { emit hScrollBarChangeRequested(config_->getMoveCursorByHorizontalScroll() ? curPos_.trackVisIdx : leftTrackVisIdx_); } redrawAll(); } void OrderListPanel::updateSizes() { QFontMetrics metrics(rowFont_); #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) rowFontWidth_ = metrics.horizontalAdvance('0'); #else rowFontWidth_ = metrics.width('0'); #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0) rowFontAscent_ = metrics.capHeight(); #else rowFontAscent_ = metrics.boundingRect('X').height(); #endif rowFontLeading_ = metrics.ascent() - rowFontAscent_ + metrics.descent() / 2; rowFontHeight_ = rowFontAscent_ + rowFontLeading_; hdFontMets_ = std::make_unique(headerFont_); headerHeight_ = hdFontMets_->height() + 5; headerFontAscent_ = hdFontMets_->ascent() + 2; /* Width & height */ widthSpace_ = rowFontWidth_ / 4; trackWidth_ = rowFontWidth_ * 3 + widthSpace_ * 2; if (config_->getShowRowNumberInHex()) { rowNumWidthCnt_ = 2; rowNumBase_ = 16; } else { rowNumWidthCnt_ = 3; rowNumBase_ = 10; } rowNumWidth_ = rowFontWidth_ * rowNumWidthCnt_ + widthSpace_; initDisplay(); } void OrderListPanel::initDisplay() { int width = geometry().width(); int ratio = iRatio(*this); // Recalculate pixmap sizes viewedRegionHeight_ = std::max((geometry().height() - headerHeight_), rowFontHeight_); int cnt = viewedRegionHeight_ / rowFontHeight_; viewedRowCnt_ = (cnt % 2) ? (cnt + 2) : (cnt + 1); viewedRowsHeight_ = viewedRowCnt_ * rowFontHeight_; viewedRowOffset_ = (viewedRowsHeight_ - viewedRegionHeight_) >> 1; viewedCenterY_ = (viewedRowsHeight_ - rowFontHeight_) >> 1; viewedCenterBaseY_ = viewedCenterY_ + rowFontAscent_ + (rowFontLeading_ >> 1); completePixmap_ = scaledQPixmap(geometry().size(), ratio); backPixmap_ = scaledQPixmap(width, viewedRowsHeight_, ratio); textPixmap_ = scaledQPixmap(width, viewedRowsHeight_, ratio); headerPixmap_ = scaledQPixmap(width, headerHeight_, ratio); } void OrderListPanel::drawList(const QRect &rect) { if (repaintable_.load()) { repaintable_.store(false); ++repaintingCnt_; // Use module data after this line if (backChanged_ || textChanged_ || headerChanged_ || orderDownCount_ || followModeChanged_) { int ratio = iRatio(*this); int maxWidth = std::min(geometry().width(), columnsWidthFromLeftToEnd_); completePixmap_.fill(palette_->odrBackColor); if (orderDownCount_ && !followModeChanged_) { quickDrawRows(maxWidth); } else { backPixmap_.fill(Qt::transparent); if (textChanged_) textPixmap_.fill(Qt::transparent); drawRows(maxWidth); } drawBorders(maxWidth); if (headerChanged_) { // headerPixmap_->fill(Qt::transparent); drawHeaders(maxWidth); } { QPainter mergePainter(&completePixmap_); QRect rowsRect(0, viewedRowOffset_, maxWidth, viewedRegionHeight_); rowsRect = scaleRect(rowsRect, ratio); QRect inViewRect(0, headerHeight_, maxWidth, viewedRegionHeight_); mergePainter.drawPixmap(inViewRect, backPixmap_, rowsRect); mergePainter.drawPixmap(inViewRect, textPixmap_, rowsRect); mergePainter.drawPixmap(QPoint(0, 0), headerPixmap_); } if (!hasFocus()) drawShadow(); backChanged_ = false; textChanged_ = false; headerChanged_ = false; followModeChanged_ = false; orderDownCount_ = 0; } --repaintingCnt_; // Used module data until this line repaintable_.store(true); } QPainter completePainter(this); completePainter.drawPixmap(rect, completePixmap_); } void OrderListPanel::drawRows(int maxWidth) { QPainter textPainter(&textPixmap_); QPainter backPainter(&backPixmap_); textPainter.setFont(rowFont_); std::vector orderRowData_; int textOffset = trackWidth_ / 2 - rowFontWidth_; /* Current row */ // Fill row backPainter.fillRect(0, viewedCenterY_, maxWidth, rowFontHeight_, hasFocus() ? palette_->odrCurEditRowColor : palette_->odrCurRowColor); if (textChanged_) { // Row number textPainter.setPen(palette_->odrRowNumColor); textPainter.drawText(1, viewedCenterBaseY_, QString("%1").arg( curPos_.row, rowNumWidthCnt_, rowNumBase_, QChar('0') ).toUpper()); } // Order data orderRowData_ = bt_->getOrderData(curSongNum_, curPos_.row); textPainter.setPen(palette_->odrCurTextColor); for (int x = rowNumWidth_, trackVisIdx = leftTrackVisIdx_; x < maxWidth; ++trackVisIdx) { if (trackVisIdx == curPos_.trackVisIdx) // Paint current cell backPainter.fillRect(x, viewedCenterY_, trackWidth_, rowFontHeight_, palette_->odrCurCellColor); if (((hovPos_.row == curPos_.row || hovPos_.row == -2) && hovPos_.trackVisIdx == trackVisIdx) || (hovPos_.trackVisIdx == -2 && hovPos_.row == curPos_.row)) // Paint hover backPainter.fillRect(x, viewedCenterY_, trackWidth_, rowFontHeight_, palette_->odrHovCellColor); if ((selLeftAbovePos_.trackVisIdx >= 0 && selLeftAbovePos_.row >= 0) && isSelectedCell(trackVisIdx, curPos_.row)) // Paint selected backPainter.fillRect(x, viewedCenterY_, trackWidth_, rowFontHeight_, palette_->odrSelCellColor); if (textChanged_) { textPainter.drawText( x + textOffset, viewedCenterBaseY_, QString("%1") .arg(orderRowData_.at(static_cast(visTracks_.at(trackVisIdx))).patten, 2, 16, QChar('0')).toUpper() ); } x += trackWidth_; } viewedCenterPos_.row = curPos_.row; int rowNum; int rowY, baseY, endY; int playOdrNum = bt_->getPlayingOrderNumber(); /* Previous rows */ viewedFirstPos_.row = curPos_.row; endY = std::max(0, viewedCenterY_ - rowFontHeight_ * curPos_.row); for (rowY = viewedCenterY_ - rowFontHeight_, baseY = viewedCenterBaseY_ - rowFontHeight_, rowNum = curPos_.row - 1; rowY >= endY; rowY -= rowFontHeight_, baseY -= rowFontHeight_, --rowNum) { QColor rowColor; if (!config_->getFollowMode() && rowNum == playOdrNum) { rowColor = palette_->odrPlayRowColor; } else { rowColor = palette_->odrDefRowColor; } // Fill row backPainter.fillRect(0, rowY, maxWidth, rowFontHeight_, rowColor); if (textChanged_) { // Row number textPainter.setPen(palette_->odrRowNumColor); textPainter.drawText(1, baseY, QString("%1").arg( rowNum, rowNumWidthCnt_, rowNumBase_, QChar('0') ).toUpper()); } // Order data orderRowData_ = bt_->getOrderData(curSongNum_, rowNum); textPainter.setPen(palette_->odrDefTextColor); for (int x = rowNumWidth_, trackVisIdx = leftTrackVisIdx_; x < maxWidth; ++trackVisIdx) { if (((hovPos_.row == rowNum || hovPos_.row == -2) && hovPos_.trackVisIdx == trackVisIdx) || (hovPos_.trackVisIdx == -2 && hovPos_.row == rowNum)) // Paint hover backPainter.fillRect(x, rowY, trackWidth_, rowFontHeight_, palette_->odrHovCellColor); if ((selLeftAbovePos_.trackVisIdx >= 0 && selLeftAbovePos_.row >= 0) && isSelectedCell(trackVisIdx, rowNum)) // Paint selected backPainter.fillRect(x, rowY, trackWidth_, rowFontHeight_, palette_->odrSelCellColor); if (textChanged_) { textPainter.drawText( x + textOffset, baseY, QString("%1") .arg(orderRowData_.at(static_cast(visTracks_.at(trackVisIdx))).patten, 2, 16, QChar('0')).toUpper() ); } x += trackWidth_; } viewedFirstPos_.row = rowNum; } /* Next rows */ viewedLastPos_.row = curPos_.row; endY = std::min(viewedRowsHeight_ - viewedRowOffset_, viewedCenterY_ + rowFontHeight_ * (static_cast(bt_->getOrderSize(curSongNum_)) - curPos_.row - 1)); for (rowY = viewedCenterY_ + rowFontHeight_, baseY = viewedCenterBaseY_ + rowFontHeight_, rowNum = curPos_.row + 1; rowY <= endY; rowY += rowFontHeight_, baseY += rowFontHeight_, ++rowNum) { QColor rowColor; if (!config_->getFollowMode() && rowNum == playOdrNum) rowColor = palette_->odrPlayRowColor; else rowColor = palette_->odrDefRowColor; // Fill row backPainter.fillRect(0, rowY, maxWidth, rowFontHeight_, rowColor); if (textChanged_) { // Row number textPainter.setPen(palette_->odrRowNumColor); textPainter.drawText(1, baseY, QString("%1").arg( rowNum, rowNumWidthCnt_, rowNumBase_, QChar('0') ).toUpper()); } // Order data orderRowData_ = bt_->getOrderData(curSongNum_, rowNum); textPainter.setPen(palette_->odrDefTextColor); for (int x = rowNumWidth_, trackVisIdx = leftTrackVisIdx_; x < maxWidth; ++trackVisIdx) { if (((hovPos_.row == rowNum || hovPos_.row == -2) && hovPos_.trackVisIdx == trackVisIdx) || (hovPos_.trackVisIdx == -2 && hovPos_.row == rowNum)) // Paint hover backPainter.fillRect(x, rowY, trackWidth_, rowFontHeight_, palette_->odrHovCellColor); if ((selLeftAbovePos_.trackVisIdx >= 0 && selLeftAbovePos_.row >= 0) && isSelectedCell(trackVisIdx, rowNum)) // Paint selected backPainter.fillRect(x, rowY, trackWidth_, rowFontHeight_, palette_->odrSelCellColor); if (textChanged_) { textPainter.drawText( x + textOffset, baseY, QString("%1") .arg(orderRowData_.at(static_cast(visTracks_.at(trackVisIdx))).patten, 2, 16, QChar('0')).toUpper() ); } x += trackWidth_; } viewedLastPos_.row = rowNum; } } void OrderListPanel::quickDrawRows(int maxWidth) { int ratio = iRatio(*this); int halfRowsCnt = viewedRowCnt_ >> 1; int shift = rowFontHeight_ * orderDownCount_; /* Move up by */ { // QPixmap::scroll() takes physical pixels, not virtual. int phShift = shift * ratio; QRect srcRect(0, 0, maxWidth, viewedRowsHeight_); srcRect = scaleRect(srcRect, ratio); textPixmap_.scroll(0, -phShift, srcRect); backPixmap_.scroll(0, -phShift, srcRect); } { int fpos = viewedCenterPos_.row + orderDownCount_ - halfRowsCnt; if (fpos >= 0) viewedFirstPos_.row = fpos; } QPainter textPainter(&textPixmap_); QPainter backPainter(&backPixmap_); textPainter.setFont(rowFont_); std::vector orderRowData_; int textOffset = trackWidth_ / 2 - rowFontWidth_; /* Clear previous cursor row, current cursor row and last rows text */ int prevY = viewedCenterY_ - shift; int lastY = viewedRowsHeight_ - shift; textPainter.setCompositionMode(QPainter::CompositionMode_Source); textPainter.fillRect(0, prevY, maxWidth, rowFontHeight_, Qt::transparent); textPainter.fillRect(0, viewedCenterY_, maxWidth, rowFontHeight_, Qt::transparent); textPainter.fillRect(0, lastY, maxWidth, shift, Qt::transparent); textPainter.setCompositionMode(QPainter::CompositionMode_SourceOver); /* Redraw previous cursor row */ { int baseY = viewedCenterBaseY_ - shift; // Fill row backPainter.fillRect(0, prevY, maxWidth, rowFontHeight_, palette_->odrDefRowColor); // Row number textPainter.setPen(palette_->odrRowNumColor); textPainter.drawText(1, baseY, QString("%1").arg( viewedCenterPos_.row, rowNumWidthCnt_, rowNumBase_, QChar('0') ).toUpper()); // Order data orderRowData_ = bt_->getOrderData(curSongNum_, viewedCenterPos_.row); textPainter.setPen(palette_->odrDefTextColor); for (int x = rowNumWidth_, trackVisIdx = leftTrackVisIdx_; x < maxWidth; ++trackVisIdx) { if (((hovPos_.row == viewedCenterPos_.row || hovPos_.row == -2) && hovPos_.trackVisIdx == trackVisIdx) || (hovPos_.trackVisIdx == -2 && hovPos_.row == viewedCenterPos_.row)) // Paint hover backPainter.fillRect(x, prevY, trackWidth_, rowFontHeight_, palette_->odrHovCellColor); if ((selLeftAbovePos_.trackVisIdx >= 0 && selLeftAbovePos_.row >= 0) && isSelectedCell(trackVisIdx, viewedCenterPos_.row)) // Paint selected backPainter.fillRect(x, prevY, trackWidth_, rowFontHeight_, palette_->odrSelCellColor); textPainter.drawText( x + textOffset, baseY, QString("%1") .arg(orderRowData_.at(static_cast(visTracks_.at(trackVisIdx))).patten, 2, 16, QChar('0')).toUpper() ); x += trackWidth_; } } /* Redraw current cursor row */ // Fill row backPainter.fillRect(0, viewedCenterY_, maxWidth, rowFontHeight_, hasFocus() ? palette_->odrCurEditRowColor : palette_->odrCurRowColor); // Row number textPainter.setPen(palette_->odrRowNumColor); textPainter.drawText(1, viewedCenterBaseY_, QString("%1").arg( curPos_.row, rowNumWidthCnt_, rowNumBase_, QChar('0') ).toUpper()); // Order data orderRowData_ = bt_->getOrderData(curSongNum_, curPos_.row); textPainter.setPen(palette_->odrCurTextColor); for (int x = rowNumWidth_, trackVisIdx = leftTrackVisIdx_; x < maxWidth; ++trackVisIdx) { if (trackVisIdx == curPos_.trackVisIdx) // Paint current cell backPainter.fillRect(x, viewedCenterY_, trackWidth_, rowFontHeight_, palette_->odrCurCellColor); if (((hovPos_.row == curPos_.row || hovPos_.row == -2) && hovPos_.trackVisIdx == trackVisIdx) || (hovPos_.trackVisIdx == -2 && hovPos_.row == curPos_.row)) // Paint hover backPainter.fillRect(x, viewedCenterY_, trackWidth_, rowFontHeight_, palette_->odrHovCellColor); if ((selLeftAbovePos_.trackVisIdx >= 0 && selLeftAbovePos_.row >= 0) && isSelectedCell(trackVisIdx, curPos_.row)) // Paint selected backPainter.fillRect(x, viewedCenterY_, trackWidth_, rowFontHeight_, palette_->odrSelCellColor); textPainter.drawText( x + textOffset, viewedCenterBaseY_, QString("%1") .arg(orderRowData_.at(static_cast(visTracks_.at(trackVisIdx))).patten, 2, 16, QChar('0')).toUpper() ); x += trackWidth_; } viewedCenterPos_ = curPos_; /* Draw new rows at last if necessary */ { int bpos = viewedCenterPos_.row + halfRowsCnt; int last = static_cast(bt_->getOrderSize(curSongNum_)) - 1; bool needClear; if (bpos < last) { needClear = false; bpos = std::exchange(viewedLastPos_.row, bpos); } else { needClear = true; bpos = std::exchange(viewedLastPos_.row, last); } int baseY = lastY + (viewedCenterBaseY_ - viewedCenterY_); while (true) { if (bpos == viewedLastPos_.row) { if (needClear) { // Clear row backPainter.setCompositionMode(QPainter::CompositionMode_Source); backPainter.fillRect(0, lastY, maxWidth, shift, Qt::transparent); } break; } ++bpos; // Fill row backPainter.fillRect(0, lastY, maxWidth, rowFontHeight_, palette_->odrDefRowColor); // Row number textPainter.setPen(palette_->odrRowNumColor); textPainter.drawText(1, baseY, QString("%1").arg( viewedLastPos_.row, rowNumWidthCnt_, rowNumBase_, QChar('0') ).toUpper()); // Order data orderRowData_ = bt_->getOrderData(curSongNum_, viewedLastPos_.row); textPainter.setPen(palette_->odrDefTextColor); for (int x = rowNumWidth_, trackVisIdx = leftTrackVisIdx_; x < maxWidth; ++trackVisIdx) { if (((hovPos_.row == viewedLastPos_.row || hovPos_.row == -2) && hovPos_.trackVisIdx == trackVisIdx) || (hovPos_.trackVisIdx == -2 && hovPos_.row == viewedLastPos_.row)) // Paint hover backPainter.fillRect(x, lastY, trackWidth_, rowFontHeight_, palette_->odrHovCellColor); if ((selLeftAbovePos_.trackVisIdx >= 0 && selLeftAbovePos_.row >= 0) && isSelectedCell(trackVisIdx, viewedLastPos_.row)) // Paint selected backPainter.fillRect(x, lastY, trackWidth_, rowFontHeight_, palette_->odrSelCellColor); textPainter.drawText( x + textOffset, baseY, QString("%1") .arg(orderRowData_.at(static_cast(visTracks_.at(trackVisIdx))).patten, 2, 16, QChar('0')).toUpper() ); x += trackWidth_; } baseY += rowFontHeight_; lastY += rowFontHeight_; } } } void OrderListPanel::drawHeaders(int maxWidth) { static const QString RHYTM_NAMES[6] = { "BD", "SD", "TOP", "HH", "TOM", "RIM" }; QPainter painter(&headerPixmap_); painter.setFont(headerFont_); painter.fillRect(0, 0, geometry().width(), headerHeight_, palette_->odrHeaderRowColor); painter.setPen(palette_->odrHeaderBorderColor); qreal bottomLineY = headerHeight_ - 0.5; painter.drawLine(QPointF(0., bottomLineY), QPointF(geometry().width(), bottomLineY)); for (int x = rowNumWidth_, trackVisIdx = leftTrackVisIdx_; x < maxWidth; ++trackVisIdx) { painter.setPen(palette_->odrHeaderBorderColor); painter.drawLine(x, 0, x, headerHeight_); QString str; auto& attrib = songStyle_.trackAttribs[static_cast(visTracks_.at(trackVisIdx))]; switch (attrib.source) { case SoundSource::FM: switch (songStyle_.type) { case SongType::Standard: str = "FM" + QString::number(attrib.channelInSource + 1); break; case SongType::FM3chExpanded: switch (attrib.channelInSource) { case 2: str = "OP1"; break; case 6: str = "OP2"; break; case 7: str = "OP3"; break; case 8: str = "OP4"; break; default: str = "FM" + QString::number(attrib.channelInSource + 1); break; } break; } break; case SoundSource::SSG: str = "SG" + QString::number(attrib.channelInSource + 1); break; case SoundSource::RHYTHM: str = RHYTM_NAMES[attrib.channelInSource]; break; case SoundSource::ADPCM: str = "AP"; break; } painter.setPen(palette_->odrHeaderTextColor); painter.drawText(QRectF(x, 0, trackWidth_, headerFontAscent_), Qt::AlignCenter, str); x += trackWidth_; } } void OrderListPanel::drawBorders(int maxWidth) { QPainter painter(&backPixmap_); painter.setPen(palette_->odrBorderColor); painter.drawLine(rowNumWidth_, 0, rowNumWidth_, backPixmap_.height()); for (int x = rowNumWidth_ + trackWidth_; x <= maxWidth; x += trackWidth_) { painter.drawLine(x, 0, x, backPixmap_.height()); } } void OrderListPanel::drawShadow() { QPainter painter(&completePixmap_); painter.fillRect(0, 0, geometry().width(), geometry().height(), palette_->odrUnfocusedShadowColor); } void OrderListPanel::moveCursorToRight(int n) { int oldLeftTrackIdx = leftTrackVisIdx_; int prevTrackIdx = curPos_.trackVisIdx; int tmp = curPos_.trackVisIdx + n; if (n > 0) { while (true) { int sub = tmp - static_cast(visTracks_.size()); if (sub < 0) { curPos_.trackVisIdx = tmp; break; } else { if (config_->getWarpCursor()) { tmp = sub; } else { curPos_.trackVisIdx = static_cast(visTracks_.size()) - 1; break; } } } } else { while (true) { int add = tmp + static_cast(visTracks_.size()); if (tmp < 0) { if (config_->getWarpCursor()) { tmp = add; } else { curPos_.trackVisIdx = 0; break; } } else { curPos_.trackVisIdx = tmp; break; } } } if (prevTrackIdx < curPos_.trackVisIdx) { while (calculateColumnsWidthWithRowNum(leftTrackVisIdx_, curPos_.trackVisIdx) > geometry().width()) ++leftTrackVisIdx_; } else { if (curPos_.trackVisIdx < leftTrackVisIdx_) leftTrackVisIdx_ = curPos_.trackVisIdx; } updateTracksWidthFromLeftToEnd(); entryCnt_ = 0; if (!isIgnoreToSlider_) { // Send to slider emit hScrollBarChangeRequested(config_->getMoveCursorByHorizontalScroll() ? curPos_.trackVisIdx : leftTrackVisIdx_); } if (!isIgnoreToPattern_) emit currentTrackChanged(curPos_.trackVisIdx); // Send to pattern editor // Request fore-background repaint if leftmost track is changed else request only background repaint if (leftTrackVisIdx_ != oldLeftTrackIdx) { headerChanged_ = true; textChanged_ = true; } backChanged_ = true; repaint(); } void OrderListPanel::moveViewToRight(int n) { leftTrackVisIdx_ += n; updateTracksWidthFromLeftToEnd(); // Move cursor and repaint all headerChanged_ = true; textChanged_ = true; moveCursorToRight(n); } void OrderListPanel::moveCursorToDown(int n) { int tmp = curPos_.row + n; int endRow = static_cast(bt_->getOrderSize(curSongNum_)); if (n > 0) { while (true) { int sub = tmp - endRow; if (sub < 0) { curPos_.row = tmp; break; } else { tmp = sub; } } } else { while (true) { int add = tmp + endRow; if (tmp < 0) { tmp = add; } else { curPos_.row = tmp; break; } } } entryCnt_ = 0; if (!isIgnoreToSlider_) // Send to slider emit vScrollBarChangeRequested(curPos_.row, static_cast(bt_->getOrderSize(curSongNum_)) - 1); if (!isIgnoreToPattern_) // Send to pattern editor emit currentOrderChanged(curPos_.row); backChanged_ = true; textChanged_ = true; repaint(); } void OrderListPanel::changeEditable() { backChanged_ = true; repaint(); } int OrderListPanel::getFullColumnSize() const { return static_cast(visTracks_.size()) - 1; } void OrderListPanel::updatePositionByOrderUpdate(bool isFirstUpdate, bool forceJump, bool trackChanged) { int prev = std::exchange(playingRow_, bt_->getPlayingOrderNumber()); if (!forceJump && !config_->getFollowMode() && prev != playingRow_) { // Repaint only background backChanged_ = true; repaint(); return; } if (trackChanged) { // Update horizontal position int trackVisIdx = std::distance(visTracks_.begin(), utils::find(visTracks_, bt_->getCurrentTrackAttribute().number)); int prevTrackIdx = std::exchange(curPos_.trackVisIdx, trackVisIdx); if (prevTrackIdx < curPos_.trackVisIdx) { while (calculateColumnsWidthWithRowNum(leftTrackVisIdx_, curPos_.trackVisIdx) > geometry().width()) { ++leftTrackVisIdx_; headerChanged_ = true; } } else { if (curPos_.trackVisIdx < leftTrackVisIdx_) { leftTrackVisIdx_ = curPos_.trackVisIdx; headerChanged_ = true; } } updateTracksWidthFromLeftToEnd(); emit hScrollBarChangeRequested(config_->getMoveCursorByHorizontalScroll() ? curPos_.trackVisIdx : leftTrackVisIdx_); } int tmp = std::exchange(curPos_.row, bt_->getCurrentOrderNumber()); int d = curPos_.row - tmp; if (d) { emit vScrollBarChangeRequested(curPos_.row, static_cast(bt_->getOrderSize(curSongNum_)) - 1); // Redraw entire area in first update and jumping order orderDownCount_ = (isFirstUpdate || d < 0 || (viewedRowCnt_ >> 1) < d) ? 0 : d; } else if (!trackChanged) return; entryCnt_ = 0; textChanged_ = true; backChanged_ = true; repaint(); } int OrderListPanel::getScrollableCountByTrack() const { int width = rowNumWidth_; size_t i = visTracks_.size(); do { --i; width += trackWidth_; if (geometry().width() < width) { return static_cast(i + 1); } } while (i); return 0; } void OrderListPanel::redrawByPatternChanged(bool ordersLengthChanged) { textChanged_ = true; // When length of orders is changed, redraw all area if (ordersLengthChanged) backChanged_ = true; repaint(); } void OrderListPanel::redrawByFocusChanged() { if (hasFocussedBefore_) { backChanged_ = true; repaint(); } else { redrawAll(); hasFocussedBefore_ = true; } } void OrderListPanel::redrawByHoverChanged() { headerChanged_ = true; backChanged_ = true; repaint(); } void OrderListPanel::redrawAll() { backChanged_ = true; textChanged_ = true; headerChanged_ = true; orderDownCount_ = 0; // Prevent quick draw repaint(); } bool OrderListPanel::enterOrder(int key) { switch (key) { case Qt::Key_0: setCellOrderNum(0x0); return true; case Qt::Key_1: setCellOrderNum(0x1); return true; case Qt::Key_2: setCellOrderNum(0x2); return true; case Qt::Key_3: setCellOrderNum(0x3); return true; case Qt::Key_4: setCellOrderNum(0x4); return true; case Qt::Key_5: setCellOrderNum(0x5); return true; case Qt::Key_6: setCellOrderNum(0x6); return true; case Qt::Key_7: setCellOrderNum(0x7); return true; case Qt::Key_8: setCellOrderNum(0x8); return true; case Qt::Key_9: setCellOrderNum(0x9); return true; case Qt::Key_A: setCellOrderNum(0xa); return true; case Qt::Key_B: setCellOrderNum(0xb); return true; case Qt::Key_C: setCellOrderNum(0xc); return true; case Qt::Key_D: setCellOrderNum(0xd); return true; case Qt::Key_E: setCellOrderNum(0xe); return true; case Qt::Key_F: setCellOrderNum(0xf); return true; default: return false; } } void OrderListPanel::setCellOrderNum(int n) { bt_->setOrderPatternDigit(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.row, n, (entryCnt_ == 1)); comStack_.lock()->push(new SetPatternToOrderQtCommand(this, curPos_, (entryCnt_ == 1))); entryCnt_ = (entryCnt_ + 1) % 2; if ((!bt_->isPlaySong() || !bt_->isFollowPlay()) && !entryCnt_) moveCursorToDown(1); } void OrderListPanel::insertOrderBelow() { if (!bt_->canAddNewOrder(curSongNum_)) return; bt_->insertOrderBelow(curSongNum_, curPos_.row); comStack_.lock()->push(new InsertOrderBelowQtCommand(this)); } void OrderListPanel::deleteOrder() { if (bt_->getOrderSize(curSongNum_) > 1) { bt_->deleteOrder(curSongNum_, curPos_.row); comStack_.lock()->push(new DeleteOrderQtCommand(this)); } } void OrderListPanel::copySelectedCells() { if (selLeftAbovePos_.row == -1) return; // Real selected region width int w = visTracks_.at(selRightBelowPos_.trackVisIdx) - visTracks_.at(selLeftAbovePos_.trackVisIdx) + 1; int h = selRightBelowPos_.row - selLeftAbovePos_.row + 1; QString str = QString("ORDER_COPY:%1,%2,") .arg(QString::number(w), QString::number(h)); for (int i = 0; i < h; ++i) { std::vector odrs = bt_->getOrderData(curSongNum_, selLeftAbovePos_.row + i); for (int j = 0; j < w; ++j) { str += QString::number(odrs.at(static_cast(visTracks_.at(selLeftAbovePos_.trackVisIdx) + j)).patten); if (i < h - 1 || j < w - 1) str += ","; } } QApplication::clipboard()->setText(str); } void OrderListPanel::pasteCopiedCells(const OrderPosition& startPos) { bool result = [&] { // Analyze text in clopboard. static const QRegularExpression COMMAND_REGEX { R"(^ORDER_COPY:(?\d+),(?\d+),(?.+)$)" }; const auto match = COMMAND_REGEX.match(QApplication::clipboard()->text()); if (!match.hasMatch()) return false; std::size_t w = match.captured("width").toUInt(); std::size_t h = match.captured("height").toUInt(); if (w == 0 || h == 0) return false; QStringList data = match.captured("data").split(","); auto unmodifiedSize = data.size(); data.removeAll(""); if (static_cast(data.size()) != w * h || data.size() != unmodifiedSize) { return false; } // Parse text. Vector2d cells(h, w); for (std::size_t i = 0; i < h; ++i) { for (std::size_t j = 0; j < w; ++j) { bool isOk{}; cells[i][j] = data[i * w + j].toInt(&isOk); if (!isOk) return false; } } // Send cells data. if (!bt_->pasteOrderCells(curSongNum_, visTracks_.at(startPos.trackVisIdx), startPos.row, cells)) { return false; } comStack_.lock()->push(new PasteCopiedDataToOrderQtCommand(this)); return true; }(); if (!result) command_result_message_box::showCommandInvokingErrorMessageBox(window()); } void OrderListPanel::clonePatterns(const OrderPosition& singlePos) { int bo, bt, eo, et; if (selLeftAbovePos_.row != -1) { bo = selLeftAbovePos_.row; bt = visTracks_.at(selLeftAbovePos_.trackVisIdx); eo = selRightBelowPos_.row; et = visTracks_.at(selRightBelowPos_.trackVisIdx); } else if (singlePos.row >= 0 && singlePos.trackVisIdx >= 0) { bo = eo = singlePos.row; bt = et = visTracks_.at(singlePos.trackVisIdx); } else return; bt_->clonePatterns(curSongNum_, bo, bt, eo, et); comStack_.lock()->push(new ClonePatternsQtCommand(this)); } void OrderListPanel::setSelectedRectangle(const OrderPosition& start, const OrderPosition& end) { if (start.trackVisIdx > end.trackVisIdx) { if (start.row > end.row) { selLeftAbovePos_ = end; selRightBelowPos_ = start; } else { selLeftAbovePos_ = { end.trackVisIdx, start.row }; selRightBelowPos_ = { start.trackVisIdx, end.row }; } } else { if (start.row > end.row) { selLeftAbovePos_ = { start.trackVisIdx, end.row }; selRightBelowPos_ = { end.trackVisIdx, start.row }; } else { selLeftAbovePos_ = start; selRightBelowPos_ = end; } } emit selected(true); backChanged_ = true; repaint(); } bool OrderListPanel::isSelectedCell(int trackIdx, int row) { return (selLeftAbovePos_.trackVisIdx <= trackIdx && selRightBelowPos_.trackVisIdx >= trackIdx && selLeftAbovePos_.row <= row && selRightBelowPos_.row >= row); } void OrderListPanel::showContextMenu(const OrderPosition& pos, const QPoint& point) { QMenu menu; // Leave Before Qt5.7.0 style due to windows xp QAction* insert = menu.addAction(tr("&Insert Order")); insert->setIcon(QIcon(":/icon/insert_order")); QObject::connect(insert, &QAction::triggered, this, [&] { insertOrderBelow(); }); QAction* remove = menu.addAction(tr("&Remove Order")); remove->setIcon(QIcon(":/icon/remove_order")); QObject::connect(remove, &QAction::triggered, this, [&] { deleteOrder(); }); QAction* duplicate = menu.addAction(tr("&Duplicate Order")); duplicate->setIcon(QIcon(":/icon/duplicate_order")); QObject::connect(duplicate, &QAction::triggered, this, &OrderListPanel::onDuplicatePressed); QAction* clonep = menu.addAction(tr("&Clone Patterns")); QAction::connect(clonep, &QAction::triggered, this, [&, pos] { clonePatterns(pos); }); QAction* cloneo = menu.addAction(tr("Clone &Order")); QObject::connect(cloneo, &QAction::triggered, this, &OrderListPanel::onCloneOrderPressed); menu.addSeparator(); QAction* moveUp = menu.addAction(tr("Move Order &Up")); moveUp->setIcon(QIcon(":/icon/order_up")); QObject::connect(moveUp, &QAction::triggered, this, [&] { onMoveOrderPressed(true); }); QAction* moveDown = menu.addAction(tr("Move Order Do&wn")); moveDown->setIcon(QIcon(":/icon/order_down")); QObject::connect(moveDown, &QAction::triggered, this, [&] { onMoveOrderPressed(false); }); menu.addSeparator(); QAction* copy = menu.addAction(tr("Cop&y")); copy->setIcon(QIcon(":/icon/copy")); QObject::connect(copy, &QAction::triggered, this, &OrderListPanel::copySelectedCells); QAction* paste = menu.addAction(tr("&Paste")); paste->setIcon(QIcon(":/icon/paste")); QObject::connect(paste, &QAction::triggered, this, [&] { pasteCopiedCells(pos); }); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) duplicate->setShortcutVisibleInContextMenu(true); clonep->setShortcutVisibleInContextMenu(true); copy->setShortcutVisibleInContextMenu(true); paste->setShortcutVisibleInContextMenu(true); #endif auto shortcuts = config_->getShortcuts(); duplicate->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::DuplicateOrder))); clonep->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::ClonePatterns))); cloneo->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::CloneOrder))); copy->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_C)); paste->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_V)); bool notCurHov = (pos.row < 0 || pos.trackVisIdx < 0); if (notCurHov) { remove->setEnabled(false); moveUp->setEnabled(false); moveDown->setEnabled(false); copy->setEnabled(false); paste->setEnabled(false); } if (!bt_->canAddNewOrder(curSongNum_)) { insert->setEnabled(false); duplicate->setEnabled(false); moveUp->setEnabled(false); moveDown->setEnabled(false); copy->setEnabled(false); paste->setEnabled(false); } QString clipText = QApplication::clipboard()->text(); if (!clipText.startsWith("ORDER_COPY")) { paste->setEnabled(false); } if (bt_->getOrderSize(curSongNum_) == 1) { remove->setEnabled(false); } if (selRightBelowPos_.row < 0 || !isSelectedCell(pos.trackVisIdx, pos.row)) { copy->setEnabled(false); // Turn off when no pattern is hilighted if (notCurHov) clonep->setEnabled(false); } if (pos.row == 0) { moveUp->setEnabled(false); } if (pos.row == static_cast(bt_->getOrderSize(curSongNum_)) - 1) { moveDown->setEnabled(false); } menu.exec(mapToGlobal(point)); } /********** Slots **********/ void OrderListPanel::onHScrollBarChanged(int num) { Ui::EventGuard eg(isIgnoreToSlider_); // Skip if position has already changed in panel if (config_->getMoveCursorByHorizontalScroll()) { if (int dif = num - curPos_.trackVisIdx) moveCursorToRight(dif); } else { if (int dif = num - leftTrackVisIdx_) moveViewToRight(dif); } } void OrderListPanel::onVScrollBarChanged(int num) { Ui::EventGuard eg(isIgnoreToSlider_); // Skip if position has already changed in panel if (int dif = num - curPos_.row) moveCursorToDown(dif); } void OrderListPanel::onPatternEditorCurrentTrackChanged(int idx) { Ui::EventGuard eg(isIgnoreToPattern_); // Skip if position has already changed in panel if (int dif = idx - curPos_.trackVisIdx) moveCursorToRight(dif); } void OrderListPanel::onPatternEditorCurrentOrderChanged(int num) { Ui::EventGuard eg(isIgnoreToPattern_); // Skip if position has already changed in panel if (int dif = num - curPos_.row) moveCursorToDown(dif); } void OrderListPanel::onOrderEdited() { // Move cursor int s = static_cast(bt_->getOrderSize(curSongNum_)); if (s <= curPos_.row) { curPos_.row = s - 1; bt_->setCurrentOrderNumber(curPos_.row); } emit orderEdited(); } void OrderListPanel::onSongLoaded() { curSongNum_ = bt_->getCurrentSongNumber(); SongType prevType = songStyle_.type; songStyle_ = bt_->getSongStyle(curSongNum_); visTracks_ = gui_utils::adaptVisibleTrackList(visTracks_, prevType, songStyle_.type); curPos_ = { visTracks_.front(), bt_->getCurrentOrderNumber() }; // Set cursor to the most lest-placed visible track if (visTracks_.front() != bt_->getCurrentTrackAttribute().number) { bt_->setCurrentTrack(visTracks_.front()); } leftTrackVisIdx_ = 0; updateTracksWidthFromLeftToEnd(); setMaximumWidth(columnsWidthFromLeftToEnd_); initDisplay(); // Call because resize event is not called during loading song hovPos_ = { -1, -1 }; mousePressPos_ = { -1, -1 }; mouseReleasePos_ = { -1, -1 }; selLeftAbovePos_ = { -1, -1 }; selRightBelowPos_ = { -1, -1 }; shiftPressedPos_ = { -1, -1 }; entryCnt_ = 0; selectAllState_ = -1; emit selected(false); redrawAll(); } void OrderListPanel::onShortcutUpdated() { } void OrderListPanel::onPastePressed() { pasteCopiedCells(curPos_); } void OrderListPanel::onSelectPressed(int type) { switch (type) { case 0: // None { selLeftAbovePos_ = { -1, -1 }; selRightBelowPos_ = { -1, -1 }; selectAllState_ = -1; emit selected(false); backChanged_ = true; repaint(); break; } case 1: // All { int max = static_cast(bt_->getOrderSize(curSongNum_)) - 1; selectAllState_ = (selectAllState_ + 1) % 2; OrderPosition start, end; if (selectAllState_) { start = { 0, 0 }; end = { static_cast(visTracks_.size() - 1), max }; } else { start = { curPos_.trackVisIdx, 0 }; end = { curPos_.trackVisIdx, max }; } setSelectedRectangle(start, end); break; } case 2: // Row { selectAllState_ = -1; OrderPosition start = { 0, curPos_.row }; OrderPosition end = { static_cast(visTracks_.size() - 1), curPos_.row }; setSelectedRectangle(start, end); break; } case 3: // Column { selectAllState_ = -1; OrderPosition start = { curPos_.trackVisIdx, 0 }; OrderPosition end = { curPos_.trackVisIdx, static_cast(bt_->getOrderSize(curSongNum_) - 1) }; setSelectedRectangle(start, end); break; } case 4: // Pattern { selectAllState_ = -1; setSelectedRectangle(curPos_, curPos_); break; } case 5: // Order { onSelectPressed(2); break; } } } void OrderListPanel::onDuplicatePressed() { bt_->duplicateOrder(curSongNum_, curPos_.row); comStack_.lock()->push(new DuplicateOrderQtCommand(this)); } void OrderListPanel::onMoveOrderPressed(bool isUp) { if ((isUp && curPos_.row == 0) || (!isUp && curPos_.row == static_cast(bt_->getOrderSize(curSongNum_)) - 1)) return; bt_->MoveOrder(curSongNum_, curPos_.row, isUp); comStack_.lock()->push(new MoveOrderQtCommand(this)); } void OrderListPanel::onClonePatternsPressed() { clonePatterns(curPos_); } void OrderListPanel::onCloneOrderPressed() { bt_->cloneOrder(curSongNum_, curPos_.row); comStack_.lock()->push(new CloneOrderQtCommand(this)); } void OrderListPanel::onFollowModeChanged() { curPos_.row = bt_->getCurrentOrderNumber(); emit vScrollBarChangeRequested(curPos_.row, static_cast(bt_->getOrderSize(curSongNum_)) - 1); // Force redraw all area followModeChanged_ = true; textChanged_ = true; backChanged_ = true; repaint(); } void OrderListPanel::onStoppedPlaySong() { followModeChanged_ = true; textChanged_ = true; backChanged_ = true; repaint(); } void OrderListPanel::onGoOrderRequested(bool toNext) { moveCursorToDown(toNext ? 1 : -1); } /********** Events **********/ bool OrderListPanel::event(QEvent* event) { switch (event->type()) { case QEvent::KeyPress: return keyPressed(dynamic_cast(event)); case QEvent::KeyRelease: return keyReleased(dynamic_cast(event)); case QEvent::HoverMove: return mouseHoverd(dynamic_cast(event)); default: return QWidget::event(event); } } bool OrderListPanel::keyPressed(QKeyEvent* event) { /* General Keys */ switch (event->key()) { case Qt::Key_Shift: shiftPressedPos_ = curPos_; return true; case Qt::Key_Left: moveCursorToRight(-1); if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; case Qt::Key_Right: moveCursorToRight(1); if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; case Qt::Key_Up: if (bt_->isPlaySong() && bt_->isFollowPlay()) { return false; } else { moveCursorToDown(-1); if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; } case Qt::Key_Down: if (bt_->isPlaySong() && bt_->isFollowPlay()) { return false; } else { moveCursorToDown(1); if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; } case Qt::Key_Home: if (bt_->isPlaySong() && bt_->isFollowPlay()) { return false; } else { moveCursorToDown(-curPos_.row); if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; } case Qt::Key_End: if (bt_->isPlaySong() && bt_->isFollowPlay()) { return false; } else { moveCursorToDown( static_cast(bt_->getOrderSize(curSongNum_)) - curPos_.row - 1); if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; } case Qt::Key_PageUp: if (bt_->isPlaySong() && bt_->isFollowPlay()) { return false; } else { moveCursorToDown(-static_cast(config_->getPageJumpLength())); if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; } case Qt::Key_PageDown: if (bt_->isPlaySong() && bt_->isFollowPlay()) { return false; } else { moveCursorToDown(static_cast(config_->getPageJumpLength())); if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; } default: { auto modifiers = event->modifiers(); if (modifiers.testFlag(Qt::NoModifier) || modifiers.testFlag(Qt::KeypadModifier)) { return enterOrder(event->key()); } return false; } } } bool OrderListPanel::keyReleased(QKeyEvent* event) { switch (event->key()) { case Qt::Key_Shift: shiftPressedPos_ = { -1, -1 }; return true; default: return false; } } void OrderListPanel::paintEvent(QPaintEvent* event) { if (bt_) { const QRect& area = event->rect(); if (area.x() == 0 && area.y() == 0) { drawList(area); } else { drawList(rect()); } } } void OrderListPanel::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); // Recalculate center row position curRowBaselineY_ = (geometry().height() + headerHeight_) / 2; curRowY_ = curRowBaselineY_ + rowFontLeading_ / 2 - rowFontAscent_; initDisplay(); redrawAll(); } void OrderListPanel::mousePressEvent(QMouseEvent* event) { mousePressPos_ = hovPos_; mouseReleasePos_ = { -1, -1 }; if (event->button() == Qt::LeftButton) { selLeftAbovePos_ = { -1, -1 }; selRightBelowPos_ = { -1, -1 }; selectAllState_ = -1; emit selected(false); } } void OrderListPanel::mouseMoveEvent(QMouseEvent* event) { if (event->buttons() & Qt::LeftButton) { if (mousePressPos_.trackVisIdx < 0 || mousePressPos_.row < 0) return; // Start point is out of range if (hovPos_.trackVisIdx >= 0) { setSelectedRectangle(mousePressPos_, hovPos_); } #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QPointF pos = event->position(); #else QPoint pos = event->pos(); #endif if (pos.x() < rowNumWidth_ && leftTrackVisIdx_ > 0) { if (config_->getMoveCursorByHorizontalScroll()) moveCursorToRight(-1); else moveViewToRight(-1); } else if (pos.x() > geometry().width() - rowNumWidth_ && hovPos_.trackVisIdx != -1) { if (config_->getMoveCursorByHorizontalScroll()) moveCursorToRight(1); else moveViewToRight(1); } if (pos.y() < headerHeight_ + rowFontHeight_) { if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(-1); } else if (pos.y() > geometry().height() - rowFontHeight_) { if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(1); } } } void OrderListPanel::mouseReleaseEvent(QMouseEvent* event) { mouseReleasePos_ = hovPos_; switch (event->button()) { case Qt::LeftButton: if (mousePressPos_ == mouseReleasePos_) { // Jump cell if (hovPos_.row >= 0 && hovPos_.trackVisIdx >= 0) { int horDif = hovPos_.trackVisIdx - curPos_.trackVisIdx; int verDif = hovPos_.row - curPos_.row; moveCursorToRight(horDif); if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(verDif); } else if (hovPos_.row == -2 && hovPos_.trackVisIdx >= 0) { // Header int horDif = hovPos_.trackVisIdx - curPos_.trackVisIdx; moveCursorToRight(horDif); } else if (hovPos_.trackVisIdx == -2 && hovPos_.row >= 0) { // Row number if (!bt_->isPlaySong() || !bt_->isFollowPlay()) { int verDif = hovPos_.row - curPos_.row; moveCursorToDown(verDif); } } } break; case Qt::RightButton: showContextMenu(mousePressPos_, event->pos()); break; case Qt::XButton1: if (!bt_->isPlaySong() || !bt_->isFollowPlay()) { moveCursorToDown(-1); } break; case Qt::XButton2: if (!bt_->isPlaySong() || !bt_->isFollowPlay()) { moveCursorToDown(1); } break; default: break; } mousePressPos_ = { -1, -1 }; mouseReleasePos_ = { -1, -1 }; } bool OrderListPanel::mouseHoverd(QHoverEvent *event) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QPointF pos = event->position(); #else QPoint pos = event->pos(); #endif OrderPosition oldPos = hovPos_; // Detect row if (pos.y() <= headerHeight_) { hovPos_.row = -2; // Header } else { if (pos.y() < curRowY_) { int tmp = curPos_.row + static_cast(std::ceil((pos.y() - curRowY_) / rowFontHeight_)) - 1; hovPos_.row = (tmp < 0) ? -1 : tmp; } else { hovPos_.row = curPos_.row + static_cast(std::floor((pos.y() - curRowY_) / rowFontHeight_)); if (hovPos_.row >= static_cast(bt_->getOrderSize(curSongNum_))) hovPos_.row = -1; } } // Detect track if (pos.x() <= rowNumWidth_) { hovPos_.trackVisIdx = -2; // Row number } else { int tmpWidth = rowNumWidth_; for (int i = leftTrackVisIdx_; ; ) { tmpWidth += trackWidth_; if (pos.x() <= tmpWidth) { hovPos_.trackVisIdx = i; break; } ++i; if (i == static_cast(visTracks_.size())) { hovPos_.trackVisIdx = -1; break; } } } if (hovPos_ != oldPos) redrawByHoverChanged(); return true; } void OrderListPanel::wheelEvent(QWheelEvent *event) { if (bt_->isPlaySong() && bt_->isFollowPlay()) return; int degree = event->angleDelta().y() / 8; moveCursorToDown(-degree / 15); } void OrderListPanel::leaveEvent(QEvent*) { // Clear mouse hover selection hovPos_ = { -1, -1 }; } BambooTracker-0.6.5/BambooTracker/gui/order_list_editor/order_list_panel.hpp000066400000000000000000000150001476276175200273230ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef ORDER_LIST_PANEL_HPP #define ORDER_LIST_PANEL_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bamboo_tracker.hpp" #include "configuration.hpp" #include "gui/order_list_editor/order_position.hpp" #include "song.hpp" #include "gui/color_palette.hpp" class OrderListPanel : public QWidget { Q_OBJECT public: explicit OrderListPanel(QWidget *parent = nullptr); void setCore(std::shared_ptr core); void setCommandStack(std::weak_ptr stack); void setConfiguration(std::shared_ptr config); void setColorPallete(std::shared_ptr palette); void changeEditable(); int getFullColumnSize() const; void updatePositionByOrderUpdate(bool isFirstUpdate, bool forceJump = false, bool trackChanged = false); int getScrollableCountByTrack() const; void copySelectedCells(); void deleteOrder(); void insertOrderBelow(); void redrawByPatternChanged(bool ordersLengthChanged = false); void redrawByFocusChanged(); void redrawByHoverChanged(); void redrawAll(); void resetEntryCount(); void waitPaintFinish(); QFont getHeaderFont() const; QFont getRowsFont() const; QFont getDefaultHeaderFont() const; QFont getDefaultRowsFont() const; void setFonts(const QFont& headerFont, const QFont& rowsFont); void setVisibleTracks(std::vector tracks); public slots: void onHScrollBarChanged(int num); void onVScrollBarChanged(int num); void onPatternEditorCurrentTrackChanged(int idx); void onPatternEditorCurrentOrderChanged(int num); void onOrderEdited(); void onSongLoaded(); void onShortcutUpdated(); void onPastePressed(); /// 0: None /// 1: All /// 2: Row /// 3: Column /// 4: Pattern /// 5: Order void onSelectPressed(int type); void onDuplicatePressed(); void onMoveOrderPressed(bool isUp); void onClonePatternsPressed(); void onCloneOrderPressed(); void onFollowModeChanged(); void onStoppedPlaySong(); void onGoOrderRequested(bool toNext); signals: void hScrollBarChangeRequested(int num); void vScrollBarChangeRequested(int num, int max); void currentTrackChanged(int idx); void currentOrderChanged(int num); void orderEdited(); void selected(bool isSelected); protected: bool event(QEvent *event) override; bool keyPressed(QKeyEvent* event); bool keyReleased(QKeyEvent* event); void paintEvent(QPaintEvent* event) override; void resizeEvent(QResizeEvent* event) override; void mousePressEvent(QMouseEvent*event) override; void mouseMoveEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; bool mouseHoverd(QHoverEvent* event); void wheelEvent(QWheelEvent* event) override; void leaveEvent(QEvent*) override; private: QPixmap completePixmap_, textPixmap_, backPixmap_, headerPixmap_; std::shared_ptr bt_; std::weak_ptr comStack_; std::shared_ptr config_; std::shared_ptr palette_; QFont rowFont_, headerFont_; QFont rowFontDef_, headerFontDef_; std::unique_ptr hdFontMets_; int rowFontWidth_, rowFontHeight_, rowFontAscent_, rowFontLeading_; int headerFontAscent_; int widthSpace_; int rowNumWidthCnt_, rowNumWidth_, rowNumBase_; int trackWidth_; int columnsWidthFromLeftToEnd_; int headerHeight_; int curRowBaselineY_; int curRowY_; std::vector visTracks_; int leftTrackVisIdx_; SongStyle songStyle_; int curSongNum_; OrderPosition curPos_, hovPos_; OrderPosition mousePressPos_, mouseReleasePos_; OrderPosition selLeftAbovePos_, selRightBelowPos_; OrderPosition shiftPressedPos_; bool isIgnoreToSlider_, isIgnoreToPattern_; int entryCnt_; int selectAllState_; int viewedRowCnt_; int viewedRegionHeight_; int viewedRowsHeight_, viewedRowOffset_, viewedCenterY_, viewedCenterBaseY_; OrderPosition viewedFirstPos_, viewedCenterPos_, viewedLastPos_; bool backChanged_, textChanged_, headerChanged_, followModeChanged_; bool hasFocussedBefore_; int orderDownCount_; std::atomic_bool repaintable_; // Recurrensive repaint guard std::atomic_int repaintingCnt_; int playingRow_; QShortcut insSc1_, insSc2_, menuSc_; void updateSizes(); void initDisplay(); void drawList(const QRect& rect); void drawRows(int maxWidth); void quickDrawRows(int maxWidth); void drawHeaders(int maxWidth); void drawBorders(int maxWidth); void drawShadow(); // NOTE: Calculated by visible tracks inline int calculateColumnsWidthWithRowNum(int beginIdx, int endIdx) const { return rowNumWidth_ + trackWidth_ * (endIdx - beginIdx + 1); } inline void updateTracksWidthFromLeftToEnd() { columnsWidthFromLeftToEnd_ = calculateColumnsWidthWithRowNum( leftTrackVisIdx_, static_cast(visTracks_.size()) - 1); } void moveCursorToRight(int n); void moveViewToRight(int n); void moveCursorToDown(int n); bool enterOrder(int key); void setCellOrderNum(int n); void pasteCopiedCells(const OrderPosition& startPos); void clonePatterns(const OrderPosition& singlePos); void setSelectedRectangle(const OrderPosition& start, const OrderPosition& end); bool isSelectedCell(int trackIdx, int row); void showContextMenu(const OrderPosition& pos, const QPoint& point); }; #endif // ORDER_LIST_PANEL_HPP BambooTracker-0.6.5/BambooTracker/gui/order_list_editor/order_position.hpp000066400000000000000000000031461476276175200270450ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef ORDER_POSITION_HPP #define ORDER_POSITION_HPP struct OrderPosition { int trackVisIdx, row; friend bool operator==(const OrderPosition& a, const OrderPosition& b); friend bool operator!=(const OrderPosition& a, const OrderPosition& b); }; inline bool operator==(const OrderPosition& a, const OrderPosition& b) { return (a.trackVisIdx == b.trackVisIdx && a.row == b.row); } inline bool operator!=(const OrderPosition& a, const OrderPosition& b) { return !(a == b); } #endif // ORDER_POSITION_HPP BambooTracker-0.6.5/BambooTracker/gui/pattern_editor/000077500000000000000000000000001476276175200226005ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/gui/pattern_editor/pattern_editor.cpp000066400000000000000000000241721476276175200263350ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "pattern_editor.hpp" #include "ui_pattern_editor.h" PatternEditor::PatternEditor(QWidget *parent) : QFrame(parent), ui(new Ui::PatternEditor), freezed_(false), songLoaded_(false), hScrollCellMove_(true) { ui->setupUi(this); installEventFilter(this); ui->panel->installEventFilter(this); ui->verticalScrollBar->installEventFilter(this); ui->panel->setFocus(); QObject::connect(ui->panel, &PatternEditorPanel::hScrollBarChangeRequested, ui->horizontalScrollBar, &QScrollBar::setValue); QObject::connect(ui->panel, &PatternEditorPanel::vScrollBarChangeRequested, this, [&](int num, int max) { if (ui->verticalScrollBar->maximum() < num) { ui->verticalScrollBar->setMaximum(max); ui->verticalScrollBar->setValue(num); } else { ui->verticalScrollBar->setValue(num); ui->verticalScrollBar->setMaximum(max); } }); QObject::connect(ui->panel, &PatternEditorPanel::currentTrackChanged, this, [&](int idx) { emit currentTrackChanged(idx); }); QObject::connect(ui->panel, &PatternEditorPanel::currentOrderChanged, this, [&](int num, int max) { emit currentOrderChanged(num, max); }); QObject::connect(ui->panel, &PatternEditorPanel::effectColsCompanded, this, [&](int num, int max) { if (ui->horizontalScrollBar->maximum() < num) { ui->horizontalScrollBar->setMaximum(max); ui->horizontalScrollBar->setValue(num); } else { ui->horizontalScrollBar->setValue(num); ui->horizontalScrollBar->setMaximum(max); } }); QObject::connect(ui->panel, &PatternEditorPanel::selected, this, [&](bool isSelected) { emit selected(isSelected); }); QObject::connect(ui->panel, &PatternEditorPanel::instrumentEntered, this, [&](int num) { emit instrumentEntered(num); }); QObject::connect(ui->panel, &PatternEditorPanel::volumeEntered, this, [&](int volume) { emit volumeEntered(volume); }); QObject::connect(ui->panel, &PatternEditorPanel::effectEntered, this, [&](QString text) { emit effectEntered(text); }); auto focusSlot = [&] { ui->panel->setFocus(); }; QObject::connect(ui->horizontalScrollBar, &QScrollBar::valueChanged, ui->panel, &PatternEditorPanel::onHScrollBarChanged); QObject::connect(ui->horizontalScrollBar, &QScrollBar::sliderPressed, this, focusSlot); QObject::connect(ui->verticalScrollBar, &QScrollBar::valueChanged, ui->panel, &PatternEditorPanel::onVScrollBarChanged); QObject::connect(ui->verticalScrollBar, &QScrollBar::sliderPressed, this, focusSlot); } PatternEditor::~PatternEditor() { delete ui; } void PatternEditor::setCore(std::shared_ptr core) { bt_ = core; ui->panel->setCore(core); } void PatternEditor::setCommandStack(std::weak_ptr stack) { ui->panel->setCommandStack(stack); } void PatternEditor::setConfiguration(std::shared_ptr config) { ui->panel->setConfiguration(config); } void PatternEditor::setColorPallete(std::shared_ptr palette) { ui->panel->setColorPallete(palette); } void PatternEditor::addActionToPanel(QAction* action) { ui->panel->addAction(action); } void PatternEditor::changeEditable() { ui->panel->changeEditable(); } void PatternEditor::updatePositionByStepUpdate(bool isFirstUpdate) { ui->panel->updatePositionByStepUpdate(isFirstUpdate); } void PatternEditor::updatepositionByPositionJump(bool trackChanged) { ui->panel->updatePositionByStepUpdate(false, true, trackChanged); } void PatternEditor::changeMarker() { ui->panel->changeMarker(); } void PatternEditor::copySelectedCells() { ui->panel->copySelectedCells(); } void PatternEditor::cutSelectedCells() { ui->panel->cutSelectedCells(); } void PatternEditor::freeze() { setUpdatesEnabled(false); freezed_ = true; ui->panel->waitPaintFinish(); } void PatternEditor::unfreeze() { freezed_ = false; setUpdatesEnabled(true); } QFont PatternEditor::getHeaderFont() const { return ui->panel->getHeaderFont(); } QFont PatternEditor::getRowsFont() const { return ui->panel->getRowsFont(); } QFont PatternEditor::getDefaultHeaderFont() const { return ui->panel->getDefaultHeaderFont(); } QFont PatternEditor::getDefaultRowsFont() const { return ui->panel->getDefaultRowsFont(); } void PatternEditor::setFonts(const QFont& headerFont, const QFont& rowsFont) { ui->panel->setFonts(headerFont, rowsFont); } void PatternEditor::setHorizontalScrollMode(bool cellBased, bool refresh) { hScrollCellMove_ = cellBased; if (refresh) updateHorizontalSliderMaximum(); } void PatternEditor::setVisibleTracks(std::vector tracks) { ui->horizontalScrollBar->setMaximum(200); // Dummy ui->panel->setVisibleTracks(tracks); updateHorizontalSliderMaximum(); } std::vector PatternEditor::getVisibleTracks() const { return ui->panel->getVisibleTracks(); } bool PatternEditor::eventFilter(QObject *watched, QEvent *event) { if (watched == this) { if (event->type() == QEvent::FocusIn) { ui->panel->setFocus(); } return false; } if (watched == ui->panel) { if (freezed_ && event->type() != QEvent::Paint) return true; switch (event->type()) { case QEvent::FocusIn: ui->panel->redrawByFocusChanged(); emit focusIn(); return false; case QEvent::FocusOut: ui->panel->redrawByFocusChanged(); emit focusOut(); return false; case QEvent::HoverEnter: case QEvent::HoverLeave: ui->panel->redrawByHoverChanged(); return false; default: return false; } } if (watched == ui->verticalScrollBar) { if (freezed_) return true; // Ignore every events switch (event->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: case QEvent::DragMove: case QEvent::Wheel: return (bt_->isPlaySong() && bt_->isFollowPlay()); default: return false; } } return false; } void PatternEditor::resizeEvent(QResizeEvent*) { // For view-based scroll updateHorizontalSliderMaximum(); } /********** Slots **********/ void PatternEditor::onOrderListCurrentTrackChanged(int idx) { ui->panel->onOrderListCurrentTrackChanged(idx); } void PatternEditor::onOrderListCrrentOrderChanged(int num) { ui->panel->onOrderListCurrentOrderChanged(num); } void PatternEditor::onOrderListEdited() { ui->panel->onOrderListEdited(); } void PatternEditor::onDefaultPatternSizeChanged() { ui->panel->onDefaultPatternSizeChanged(); } void PatternEditor::onShortcutUpdated() { ui->panel->onShortcutUpdated(); } void PatternEditor::onPatternDataGlobalChanged() { ui->panel->redrawByPatternChanged(); } void PatternEditor::setPatternHighlight1Count(int count) { ui->panel->setPatternHighlight1Count(count); } void PatternEditor::setPatternHighlight2Count(int count) { ui->panel->setPatternHighlight2Count(count); } void PatternEditor::setEditableStep(int n) { ui->panel->setEditableStep(n); } void PatternEditor::onSongLoaded() { ui->horizontalScrollBar->setValue(0); ui->panel->onSongLoaded(); songLoaded_ = true; updateHorizontalSliderMaximum(); ui->verticalScrollBar->setMaximum(static_cast(bt_->getPatternSizeFromOrderNumber( bt_->getCurrentSongNumber(), bt_->getCurrentOrderNumber())) - 1); ui->verticalScrollBar->setValue(0); } void PatternEditor::onDeletePressed() { ui->panel->onDeletePressed(); } void PatternEditor::onPastePressed() { ui->panel->onPastePressed(); } void PatternEditor::onPasteMixPressed() { ui->panel->onPasteMixPressed(); } void PatternEditor::onPasteOverwritePressed() { ui->panel->onPasteOverwritePressed(); } void PatternEditor::onPasteInsertPressed() { ui->panel->onPasteInsertPressed(); } void PatternEditor::onSelectPressed(int type) { ui->panel->onSelectPressed(type); } void PatternEditor::onTransposePressed(bool isOctave, bool isIncreased) { int semitone = isOctave ? (isIncreased ? 12 : -12) : (isIncreased ? 1 : -1); ui->panel->onNoteTransposePressed(semitone); } void PatternEditor::onChangeValuesPressed(bool isCoarse, bool isIncreased) { int val = isCoarse ? (isIncreased ? 16 : -16) : (isIncreased ? 1 : -1); ui->panel->onChangeValuesPressed(val); } void PatternEditor::onToggleTrackPressed() { ui->panel->onToggleTrackPressed(); } void PatternEditor::onSoloTrackPressed() { ui->panel->onSoloTrackPressed(); } void PatternEditor::onExpandPressed() { ui->panel->onExpandPressed(); } void PatternEditor::onShrinkPressed() { ui->panel->onShrinkPressed(); } void PatternEditor::onInterpolatePressed() { ui->panel->onInterpolatePressed(); } void PatternEditor::onReversePressed() { ui->panel->onReversePressed(); } void PatternEditor::onReplaceInstrumentPressed() { ui->panel->onReplaceInstrumentPressed(); } void PatternEditor::onFollowModeChanged() { ui->panel->onFollowModeChanged(); } void PatternEditor::onStoppedPlaySong() { ui->panel->redrawPatterns(); } void PatternEditor::onDuplicateInstrumentsRemoved() { ui->panel->redrawByPatternChanged(); } void PatternEditor::onPlayStepPressed() { ui->panel->onPlayStepPressed(); } void PatternEditor::updateHorizontalSliderMaximum() { if (!ui->panel->isReadyCore() || !songLoaded_) return; int max = hScrollCellMove_ ? ui->panel->getFullColmunSize() : ui->panel->getScrollableCountByTrack(); ui->horizontalScrollBar->setMaximum(max); } BambooTracker-0.6.5/BambooTracker/gui/pattern_editor/pattern_editor.hpp000066400000000000000000000077461476276175200263520ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef PATTERN_EDITOR_HPP #define PATTERN_EDITOR_HPP #include #include #include #include #include "bamboo_tracker.hpp" #include "configuration.hpp" #include "gui/color_palette.hpp" namespace Ui { class PatternEditor; } class PatternEditor : public QFrame { Q_OBJECT public: explicit PatternEditor(QWidget *parent = nullptr); ~PatternEditor() override; void setCore(std::shared_ptr core); void setCommandStack(std::weak_ptr stack); void setConfiguration(std::shared_ptr config); void setColorPallete(std::shared_ptr palette); void addActionToPanel(QAction* action); void changeEditable(); void updatePositionByStepUpdate(bool isFirstUpdate); void updatepositionByPositionJump(bool trackChanged = false); void changeMarker(); void copySelectedCells(); void cutSelectedCells(); void freeze(); void unfreeze(); QFont getHeaderFont() const; QFont getRowsFont() const; QFont getDefaultHeaderFont() const; QFont getDefaultRowsFont() const; void setFonts(const QFont& headerFont, const QFont& rowsFont); void setHorizontalScrollMode(bool cellBased, bool refresh = true); void setVisibleTracks(std::vector tracks); std::vector getVisibleTracks() const; signals: void currentTrackChanged(int idx); void currentOrderChanged(int num, int max); void focusIn(); void focusOut(); void selected(bool isSelected); void instrumentEntered(int num); void volumeEntered(int volume); void effectEntered(QString text); protected: bool eventFilter(QObject *watched, QEvent *event) override; void resizeEvent(QResizeEvent*) override; public slots: void onOrderListCurrentTrackChanged(int idx); void onOrderListCrrentOrderChanged(int num); void onOrderListEdited(); void onDefaultPatternSizeChanged(); void onShortcutUpdated(); void onPatternDataGlobalChanged(); void setPatternHighlight1Count(int count); void setPatternHighlight2Count(int count); void setEditableStep(int n); void onSongLoaded(); void onDeletePressed(); void onPastePressed(); void onPasteMixPressed(); void onPasteInsertPressed(); void onPasteOverwritePressed(); /// 0: None /// 1: All /// 2: Row /// 3: Column /// 4: Pattern /// 5: Order void onSelectPressed(int type); void onTransposePressed(bool isOctave, bool isIncreased); void onChangeValuesPressed(bool isCoarse, bool isIncreased); void onToggleTrackPressed(); void onSoloTrackPressed(); void onExpandPressed(); void onShrinkPressed(); void onInterpolatePressed(); void onReversePressed(); void onReplaceInstrumentPressed(); void onFollowModeChanged(); void onStoppedPlaySong(); void onDuplicateInstrumentsRemoved(); void onPlayStepPressed(); private: Ui::PatternEditor *ui; std::shared_ptr bt_; bool freezed_; bool songLoaded_; bool hScrollCellMove_; void updateHorizontalSliderMaximum(); }; #endif // PATTERN_EDITOR_HPP BambooTracker-0.6.5/BambooTracker/gui/pattern_editor/pattern_editor.ui000066400000000000000000000033461476276175200261700ustar00rootroot00000000000000 PatternEditor 0 0 400 300 Frame QFrame::Panel QFrame::Sunken 0 0 0 0 0 Qt::Horizontal Qt::Vertical PatternEditorPanel QWidget
gui/pattern_editor/pattern_editor_panel.hpp
1
BambooTracker-0.6.5/BambooTracker/gui/pattern_editor/pattern_editor_panel.cpp000066400000000000000000003405621476276175200275200ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "pattern_editor_panel.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include #endif #include #include #include #include #include #include #include #include "midi/midi.hpp" #include "command/pattern/set_effect_value_to_step_command.hpp" #include "jamming.hpp" #include "note.hpp" #include "step.hpp" #include "bamboo_tracker_defs.hpp" #include "gui/dpi.hpp" #include "gui/event_guard.hpp" #include "gui/command/pattern/pattern_commands_qt.hpp" #include "gui/effect_description.hpp" #include "gui/jam_layout.hpp" #include "gui/note_name_manager.hpp" #include "gui/gui_utils.hpp" #include "gui/command_result_message_box.hpp" #include "utils.hpp" using Dpi::scaledQPixmap; using Dpi::iRatio; using Dpi::scaleRect; PatternEditorPanel::PatternEditorPanel(QWidget *parent) : QWidget(parent), config_(std::make_shared()), // Dummy stepFontWidth_(0), stepFontHeight_(0), stepFontAscent_(0), stepFontLeading_(0), headerFontAscent_(0), widthSpace_(0), widthSpaceDbl_(0), stepNumWidthCnt_(0), stepNumWidth_(0), stepNumBase_(0), baseTrackWidth_(0), toneNameWidth_(0), instWidth_(0), volWidth_(0), effWidth_(0), effIDWidth_(0), effValWidth_(0), tracksWidthFromLeftToEnd_(0), hdMuteToggleWidth_(0), hdEffCompandButtonWidth_(0), headerHeight_(0), hdPlusY_(0), hdMinusY_(0), curRowBaselineY_(0), curRowY_(0), visTracks_(1), // Dummy rightEffn_(16), // Dummy leftTrackVisIdx_(0), curSongNum_(0), curPos_{ 0, 0, 0, 0, }, hovPos_{ -1, -1, -1, -1 }, mousePressPos_{ -1, -1, -1, -1 }, mouseReleasePos_{ -1, -1, -1, -1 }, selLeftAbovePos_{ -1, -1, -1, -1 }, selRightBelowPos_{ -1, -1, -1, -1 }, shiftPressedPos_{ -1, -1, -1, -1 }, doubleClickPos_{ -1, -1, -1, -1 }, markerPos_{ -1, -1, -1, -1 }, isIgnoreToSlider_(false), isIgnoreToOrder_(false), isPressedPlus_(false), isPressedMinus_(false), entryCnt_(0), selectAllState_(-1), isMuteElse_(false), hl1Cnt_(4), hl2Cnt_(16), editableStepCnt_(1), viewedRowCnt_(1), viewedRowsHeight_(0), viewedRowOffset_(0), viewedCenterY_(0), viewedCenterBaseY_(0), backChanged_(false), textChanged_(false), foreChanged_(false), headerChanged_(false), focusChanged_(false), followModeChanged_(false), hasFocussedBefore_(false), stepDownCount_(0), repaintable_(true), repaintingCnt_(0), isInitedFirstMod_(false), upSc_(Qt::Key_Up, this, nullptr, nullptr, Qt::WidgetShortcut), upWSSc_(Qt::SHIFT | Qt::Key_Up, this, nullptr, nullptr, Qt::WidgetShortcut), dnSc_(Qt::Key_Down, this, nullptr, nullptr, Qt::WidgetShortcut), dnWSSc_(Qt::SHIFT | Qt::Key_Down, this, nullptr, nullptr, Qt::WidgetShortcut), pgUpSc_(Qt::Key_PageUp, this, nullptr, nullptr, Qt::WidgetShortcut), pgUpWSSc_(Qt::SHIFT | Qt::Key_PageUp, this, nullptr, nullptr, Qt::WidgetShortcut), pgDnSc_(Qt::Key_PageDown, this, nullptr, nullptr, Qt::WidgetShortcut), pgDnWSSc_(Qt::SHIFT | Qt::Key_PageDown, this, nullptr, nullptr, Qt::WidgetShortcut), homeSc_(Qt::Key_Home, this, nullptr, nullptr, Qt::WidgetShortcut), homeWSSc_(Qt::SHIFT | Qt::Key_Home, this, nullptr, nullptr, Qt::WidgetShortcut), endSc_(Qt::Key_End, this, nullptr, nullptr, Qt::WidgetShortcut), endWSSc_(Qt::SHIFT | Qt::Key_End, this, nullptr, nullptr, Qt::WidgetShortcut), hlUpSc_(QKeySequence(), this, nullptr, nullptr, Qt::WidgetShortcut), hlUpWSSc_(QKeySequence(), this, nullptr, nullptr, Qt::WidgetShortcut), hlDnSc_(QKeySequence(), this, nullptr, nullptr, Qt::WidgetShortcut), hlDnWSSc_(QKeySequence(), this, nullptr, nullptr, Qt::WidgetShortcut), ltSc_(Qt::Key_Left, this, nullptr, nullptr, Qt::WidgetShortcut), ltWSSc_(Qt::SHIFT | Qt::Key_Left, this, nullptr, nullptr, Qt::WidgetShortcut), rtSc_(Qt::Key_Right, this, nullptr, nullptr, Qt::WidgetShortcut), rtWSSc_(Qt::SHIFT | Qt::Key_Right, this, nullptr, nullptr, Qt::WidgetShortcut), keyOffSc_(QKeySequence(), this, nullptr, nullptr, Qt::WidgetShortcut), keyCutSc_(QKeySequence(), this, nullptr, nullptr, Qt::WidgetShortcut), echoBufSc_(QKeySequence(), this, nullptr, nullptr, Qt::WidgetShortcut), stepMvUpSc_(Qt::ALT | Qt::Key_Up, this, nullptr, nullptr, Qt::WidgetShortcut), stepMvDnSc_(Qt::ALT | Qt::Key_Down, this, nullptr, nullptr, Qt::WidgetShortcut), expandColSc_(QKeySequence(), this, nullptr, nullptr, Qt::WidgetShortcut), shrinkColSc_(QKeySequence(), this, nullptr, nullptr, Qt::WidgetShortcut) { setAttribute(Qt::WA_Hover); setFocusPolicy(Qt::ClickFocus); setContextMenuPolicy(Qt::CustomContextMenu); // Initialize font headerFontDef_ = [] { auto font = QApplication::font(); font.setPointSize(10); return font; }(); headerFont_ = headerFontDef_; stepFontDef_ = [] { QFont font("Monospace", 10); font.setStyleHint(QFont::TypeWriter); font.setStyleStrategy(QFont::PreferMatch); // Get actually used font QFontInfo info(font); return QFont(info.family(), info.pointSize()); }(); stepFont_ = stepFontDef_; updateSizes(); // Track visibility songStyle_.type = SongType::Standard; // Dummy songStyle_.trackAttribs.push_back({ 0, SoundSource::FM, 0 }); // Dummy std::iota(visTracks_.begin(), visTracks_.end(), 0); // Shortcuts auto vMoveLam = [&] (bool isShift, auto getFunc) { // For lazy evaluation if (bt_->isPlaySong() && bt_->isFollowPlay()) return; moveCursorToDown(getFunc()); checkSelectionByCursorMove(isShift); }; auto upLam = [&] { return (editableStepCnt_ ? -editableStepCnt_ : -1); }; QObject::connect(&upSc_, &QShortcut::activated, this, [vMoveLam, upLam] { vMoveLam(false, upLam); }); QObject::connect(&upWSSc_, &QShortcut::activated, this, [vMoveLam, upLam] { vMoveLam(true, upLam); }); auto dnLam = [&] { return (editableStepCnt_ ? editableStepCnt_ : 1); }; QObject::connect(&dnSc_, &QShortcut::activated, this, [vMoveLam, dnLam] { vMoveLam(false, dnLam); }); QObject::connect(&dnWSSc_, &QShortcut::activated, this, [vMoveLam, dnLam] { vMoveLam(true, dnLam); }); auto pgUpLam = [&] { return -static_cast(config_->getPageJumpLength()); }; QObject::connect(&pgUpSc_, &QShortcut::activated, this, [vMoveLam, pgUpLam] { vMoveLam(false, pgUpLam); }); QObject::connect(&pgUpWSSc_, &QShortcut::activated, this, [vMoveLam, pgUpLam] { vMoveLam(true, pgUpLam); }); auto pgDnLam = [&] { return static_cast(config_->getPageJumpLength()); }; QObject::connect(&pgDnSc_, &QShortcut::activated, this, [vMoveLam, pgDnLam] { vMoveLam(false, pgDnLam); }); QObject::connect(&pgDnWSSc_, &QShortcut::activated, this, [vMoveLam, pgDnLam] { vMoveLam(true, pgDnLam); }); auto homeLam = [&] { return -curPos_.step; }; QObject::connect(&homeSc_, &QShortcut::activated, this, [vMoveLam, homeLam] { vMoveLam(false, homeLam); }); QObject::connect(&homeWSSc_, &QShortcut::activated, this, [vMoveLam, homeLam] { vMoveLam(true, homeLam); }); auto endLam = [&] { return (static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order)) - curPos_.step - 1); }; QObject::connect(&endSc_, &QShortcut::activated, this, [vMoveLam, endLam] { vMoveLam(false, endLam); }); QObject::connect(&endWSSc_, &QShortcut::activated, this, [vMoveLam, endLam] { vMoveLam(true, endLam); }); auto hlUpLam = [&] { int base; if (curPos_.step) { base = curPos_.step; } else { base = static_cast(bt_->getPatternSizeFromOrderNumber( curSongNum_, (curPos_.order) ? (curPos_.order - 1) : (static_cast(bt_->getOrderSize(curSongNum_)) - 1))); } return ((base - 1) / hl1Cnt_ * hl1Cnt_ - base); }; QObject::connect(&hlUpSc_, &QShortcut::activated, this, [vMoveLam, hlUpLam] { vMoveLam(false, hlUpLam); }); QObject::connect(&hlUpWSSc_, &QShortcut::activated, this, [vMoveLam, hlUpLam] { vMoveLam(true, hlUpLam); }); auto hlDnLam = [&] { int next = std::min((curPos_.step / hl1Cnt_ + 1) * hl1Cnt_, static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order))); return (next - curPos_.step); }; QObject::connect(&hlDnSc_, &QShortcut::activated, this, [vMoveLam, hlDnLam] { vMoveLam(false, hlDnLam); }); QObject::connect(&hlDnWSSc_, &QShortcut::activated, this, [vMoveLam, hlDnLam] { vMoveLam(true, hlDnLam); }); auto ltRtLam = [&] (bool isLeft, bool isShift) { moveCursorToRight(isLeft ? -1 : 1); checkSelectionByCursorMove(isShift); }; QObject::connect(<Sc_, &QShortcut::activated, this, [ltRtLam] { ltRtLam(true, false); }); QObject::connect(<WSSc_, &QShortcut::activated, this, [ltRtLam] { ltRtLam(true, true); }); QObject::connect(&rtSc_, &QShortcut::activated, this, [ltRtLam] { ltRtLam(false, false); }); QObject::connect(&rtWSSc_, &QShortcut::activated, this, [ltRtLam] { ltRtLam(false, true); }); QObject::connect(&keyOffSc_, &QShortcut::activated, this, [&] { if (!bt_->isJamMode() && curPos_.colInTrack == 0) { bt_->setStepKeyOff(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.order, curPos_.step); comStack_.lock()->push(new SetKeyOffToStepQtCommand(this)); if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(editableStepCnt_); } }); QObject::connect(&keyCutSc_, &QShortcut::activated, this, [&] { if (!bt_->isJamMode() && curPos_.colInTrack == 0) { bt_->setStepKeyCut(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.order, curPos_.step); comStack_.lock()->push(new SetKeyCutToStepQtCommand(this)); if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(editableStepCnt_); } }); QObject::connect(&echoBufSc_, &QShortcut::activated, this, [&] { if (!bt_->isJamMode() && curPos_.colInTrack == 0) { int n = bt_->getCurrentOctave(); if (n > 3) n = 3; bt_->setEchoBufferAccess(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.order, curPos_.step, n); comStack_.lock()->push(new SetEchoBufferAccessQtCommand(this)); if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(editableStepCnt_); } }); QObject::connect(&stepMvUpSc_, &QShortcut::activated, this, [&] { if ((!bt_->isPlaySong() || !bt_->isFollowPlay()) && !bt_->isJamMode()) deletePreviousStep(); }); QObject::connect(&stepMvDnSc_, &QShortcut::activated, this, [&] { if ((!bt_->isPlaySong() || !bt_->isFollowPlay()) && !bt_->isJamMode()) { insertStep(); moveCursorToDown(1); } }); QObject::connect(&expandColSc_, &QShortcut::activated, this, [&] { onExpandEffectColumnPressed(curPos_.trackVisIdx); }); QObject::connect(&shrinkColSc_, &QShortcut::activated, this, [&] { onShrinkEffectColumnPressed(curPos_.trackVisIdx); }); onShortcutUpdated(); // MIDI midiKeyEventMethod_ = metaObject()->indexOfSlot("midiKeyEvent(uchar,uchar,uchar)"); Q_ASSERT(midiKeyEventMethod_ != -1); MidiInterface::getInstance().installInputHandler(&midiThreadReceivedEvent, this); } PatternEditorPanel::~PatternEditorPanel() { MidiInterface::getInstance().uninstallInputHandler(&midiThreadReceivedEvent, this); } void PatternEditorPanel::funcResize() { // Recalculate center row position curRowY_ = (geometry().height() + headerHeight_ - stepFontHeight_) >> 1; curRowBaselineY_ = curRowY_ + stepFontAscent_ - (stepFontLeading_ >> 1); initDisplay(); } void PatternEditorPanel::updateSizes() { QFontMetrics metrics(stepFont_); #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) stepFontWidth_ = metrics.horizontalAdvance('0'); #else stepFontWidth_ = metrics.width('0'); #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0) stepFontAscent_ = metrics.capHeight(); #else stepFontAscent_ = metrics.boundingRect('X').height(); #endif stepFontLeading_ = metrics.ascent() - stepFontAscent_ + metrics.descent() / 2; stepFontHeight_ = stepFontAscent_ + stepFontLeading_; QFontMetrics m(headerFont_); headerFontAscent_ = m.ascent() + 2; /* Width & height */ widthSpace_ = stepFontWidth_ / 5 * 2; widthSpaceDbl_ = widthSpace_ * 2; stepNumWidthCnt_ = config_->getShowRowNumberInHex() ? 2 : 3; if (config_->getShowRowNumberInHex()) { stepNumWidthCnt_ = 2; stepNumBase_ = 16; } else { stepNumWidthCnt_ = 3; stepNumBase_ = 10; } stepNumWidth_ = stepFontWidth_ * stepNumWidthCnt_ + widthSpace_; toneNameWidth_ = stepFontWidth_ * 3; instWidth_ = stepFontWidth_ * 2; volWidth_ = stepFontWidth_ * 2; effIDWidth_ = stepFontWidth_ * 2; effValWidth_ = stepFontWidth_ * 2; effWidth_ = effIDWidth_ + effValWidth_ + widthSpaceDbl_; baseTrackWidth_ = toneNameWidth_ + instWidth_ + volWidth_ + effIDWidth_ + effValWidth_ + widthSpaceDbl_ * 4; #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) hdEffCompandButtonWidth_ = m.horizontalAdvance("+"); #else hdEffCompandButtonWidth_ = m.width("+"); #endif hdMuteToggleWidth_ = baseTrackWidth_ - hdEffCompandButtonWidth_ - stepFontWidth_ / 2 * 3; headerHeight_ = m.height() * 2 + 1; hdPlusY_ = headerHeight_ / 4 + m.lineSpacing() / 2 - m.leading() / 2 - m.descent(); hdMinusY_ = headerHeight_ / 2 + hdPlusY_; initDisplay(); } void PatternEditorPanel::initDisplay() { int width = geometry().width(); int ratio = iRatio(*this); // Recalculate pixmap sizes viewedRegionHeight_ = std::max((geometry().height() - headerHeight_), stepFontHeight_); int cnt = viewedRegionHeight_ / stepFontHeight_; viewedRowCnt_ = (cnt % 2) ? (cnt + 2) : (cnt + 1); viewedRowsHeight_ = viewedRowCnt_ * stepFontHeight_; viewedRowOffset_ = (viewedRowsHeight_ - viewedRegionHeight_) >> 1; viewedCenterY_ = (viewedRowsHeight_ - stepFontHeight_) >> 1; viewedCenterBaseY_ = viewedCenterY_ + stepFontAscent_ + (stepFontLeading_ >> 1); completePixmap_ = scaledQPixmap(geometry().size(), ratio); backPixmap_ = scaledQPixmap(width, viewedRowsHeight_, ratio); textPixmap_ = scaledQPixmap(width, viewedRowsHeight_, ratio); forePixmap_ = scaledQPixmap(width, viewedRowsHeight_, ratio); headerPixmap_ = scaledQPixmap(width, headerHeight_, ratio); } void PatternEditorPanel::setCore(std::shared_ptr core) { bt_ = core; } bool PatternEditorPanel::isReadyCore() const { return (bt_ != nullptr); } void PatternEditorPanel::setCommandStack(std::weak_ptr stack) { comStack_ = stack; } void PatternEditorPanel::setConfiguration(std::shared_ptr config) { config_ = config; } void PatternEditorPanel::setColorPallete(std::shared_ptr palette) { palette_ = palette; } void PatternEditorPanel::waitPaintFinish() { while (true) { if (repaintingCnt_.load()) std::this_thread::sleep_for(std::chrono::milliseconds(10)); else { curPos_ = { 0, 0, 0, 0 }; // Init return; } } } QFont PatternEditorPanel::getHeaderFont() const { return headerFont_; } QFont PatternEditorPanel::getRowsFont() const { return stepFont_; } QFont PatternEditorPanel::getDefaultHeaderFont() const { return headerFontDef_; } QFont PatternEditorPanel::getDefaultRowsFont() const { return stepFontDef_; } void PatternEditorPanel::setFonts(const QFont& headerFont, const QFont& rowsFont) { headerFont_ = headerFont; stepFont_ = rowsFont; updateSizes(); updateTracksWidthFromLeftToEnd(); redrawAll(); } void PatternEditorPanel::setVisibleTracks(std::vector tracks) { visTracks_ = tracks; rightEffn_.resize(visTracks_.size()); std::transform(visTracks_.begin(), visTracks_.end(), rightEffn_.begin(), [&](int t) { return static_cast(bt_->getEffectDisplayWidth(curSongNum_, t)); }); int max = static_cast(tracks.size()); bool cond = (max <= curPos_.trackVisIdx); if (cond) curPos_.trackVisIdx = max; leftTrackVisIdx_ = std::min(leftTrackVisIdx_, curPos_.trackVisIdx); updateTracksWidthFromLeftToEnd(); bt_->setCurrentTrack(visTracks_.at(curPos_.trackVisIdx)); emit currentTrackChanged(curPos_.trackVisIdx); if (cond) { if (config_->getMoveCursorByHorizontalScroll()) emit hScrollBarChangeRequested(calculateColNumInRow(curPos_.trackVisIdx, curPos_.colInTrack)); else emit hScrollBarChangeRequested(leftTrackVisIdx_); } redrawAll(); } std::vector PatternEditorPanel::getVisibleTracks() const { return visTracks_; } void PatternEditorPanel::redrawByPatternChanged(bool patternSizeChanged) { textChanged_ = true; // When pattern size is changed, redraw all area if (patternSizeChanged) { backChanged_ = true; foreChanged_ = true; } repaint(); } void PatternEditorPanel::redrawByFocusChanged() { if (hasFocussedBefore_) { focusChanged_ = true; repaint(); } else { redrawAll(); hasFocussedBefore_ = true; } } void PatternEditorPanel::redrawByHoverChanged() { headerChanged_ = true; backChanged_ = true; repaint(); } void PatternEditorPanel::redrawByMaskChanged() { foreChanged_ = true; headerChanged_ = true; repaint(); } void PatternEditorPanel::redrawPatterns() { backChanged_ = true; textChanged_ = true; foreChanged_ = true; repaint(); } void PatternEditorPanel::redrawAll() { headerChanged_ = true; redrawPatterns(); } void PatternEditorPanel::resetEntryCount() { entryCnt_ = 0; } void PatternEditorPanel::drawPattern(const QRect &rect) { if (repaintable_.load()) { repaintable_.store(false); ++repaintingCnt_; // Use module data after this line int ratio = iRatio(*this); if (rect.size() * ratio != completePixmap_.size()) { // Prevent resize event was failed funcResize(); headerChanged_ = true; backChanged_ = true; textChanged_ = true; foreChanged_ = true; } if (backChanged_ || textChanged_ || foreChanged_ || headerChanged_ || focusChanged_ || stepDownCount_ || followModeChanged_) { int maxWidth = std::min(rect.width(), tracksWidthFromLeftToEnd_); completePixmap_.fill(palette_->ptnBackColor); if (!focusChanged_) { if (stepDownCount_ && !followModeChanged_) { quickDrawRows(maxWidth); } else { backPixmap_.fill(Qt::transparent); if (textChanged_) textPixmap_.fill(Qt::transparent); if (foreChanged_) forePixmap_.fill(Qt::transparent); drawRows(maxWidth); } drawBorders(maxWidth); if (headerChanged_) { // headerPixmap_->fill(Qt::transparent); drawHeaders(maxWidth); } } { QPainter mergePainter(&completePixmap_); QRect rowsRect(0, viewedRowOffset_, maxWidth, viewedRegionHeight_); rowsRect = scaleRect(rowsRect, ratio); QRect inViewRect(0, headerHeight_, maxWidth, viewedRegionHeight_); mergePainter.drawPixmap(inViewRect, backPixmap_, rowsRect); mergePainter.drawPixmap(inViewRect, textPixmap_, rowsRect); mergePainter.drawPixmap(inViewRect, forePixmap_, rowsRect); mergePainter.drawPixmap(QPoint(0, 0), headerPixmap_); } if (!hasFocus()) drawShadow(); backChanged_ = false; textChanged_ = false; foreChanged_ = false; headerChanged_ = false; focusChanged_ = false; followModeChanged_ = false; stepDownCount_ = 0; } --repaintingCnt_; // Used module data until this line repaintable_.store(true); } QPainter completePainter(this); completePainter.drawPixmap(rect, completePixmap_); } void PatternEditorPanel::drawRows(int maxWidth) { QPainter forePainter(&forePixmap_); QPainter textPainter(&textPixmap_); QPainter backPainter(&backPixmap_); textPainter.setFont(stepFont_); /* Current row */ // Fill row backPainter.fillRect(0, viewedCenterY_, maxWidth, stepFontHeight_, bt_->isJamMode() ? palette_->ptnCurStepColor : palette_->ptnCurEditStepColor); // Step number if (markerPos_.isEqualRows(curPos_)) backPainter.fillRect(0, viewedCenterY_, stepNumWidth_, stepFontHeight_, palette_->ptnMarkerColor); // Paint marker if (hovPos_.trackVisIdx == -2 && hovPos_.isEqualRows(curPos_)) backPainter.fillRect(0, viewedCenterY_, stepNumWidth_, stepFontHeight_, palette_->ptnHovCellColor); // Paint hover if (textChanged_) { if (curPos_.step % hl2Cnt_) { textPainter.setPen(!(curPos_.step % hl2Cnt_) ? palette_->ptnHl1StepNumColor : !(curPos_.step % hl1Cnt_) ? palette_->ptnHl1StepNumColor : palette_->ptnDefStepNumColor); } else { textPainter.setPen(palette_->ptnHl2StepNumColor); } textPainter.drawText(1, viewedCenterBaseY_, QString("%1").arg(curPos_.step, stepNumWidthCnt_, stepNumBase_, QChar('0')).toUpper()); } // Step data for (int x = stepNumWidth_, trackVisIdx = leftTrackVisIdx_; x < maxWidth; ++trackVisIdx) { x += drawStep(forePainter, textPainter, backPainter, trackVisIdx, curPos_.order, curPos_.step, x, viewedCenterBaseY_, viewedCenterY_); } viewedCenterPos_ = curPos_; int stepNum, odrNum; int rowY, baseY; int playOdrNum = bt_->getPlayingOrderNumber(); int playStepNum = bt_->getPlayingStepNumber(); /* Previous rows */ viewedFirstPos_ = curPos_; for (rowY = viewedCenterY_ - stepFontHeight_, baseY = viewedCenterBaseY_ - stepFontHeight_, stepNum = curPos_.step - 1, odrNum = curPos_.order; rowY >= 0; rowY -= stepFontHeight_, baseY -= stepFontHeight_, --stepNum) { if (stepNum == -1) { if (odrNum == 0) { break; } else if (config_->getShowPreviousNextOrders()) { --odrNum; stepNum = static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, odrNum)) - 1; } else { break; } } QColor rowColor; if (!config_->getFollowMode() && odrNum == playOdrNum && stepNum == playStepNum) { rowColor = palette_->ptnPlayStepColor; } else { rowColor = !(stepNum % hl2Cnt_) ? palette_->ptnHl2StepColor : !(stepNum % hl1Cnt_) ? palette_->ptnHl1StepColor : palette_->ptnDefStepColor; } // Fill row backPainter.fillRect(0, rowY, maxWidth, stepFontHeight_, rowColor); // Step number if (markerPos_.isEqualRows(odrNum, stepNum)) backPainter.fillRect(0, rowY, stepNumWidth_, stepFontHeight_, palette_->ptnMarkerColor); // Paint marker if (hovPos_.trackVisIdx == -2 && hovPos_.isEqualRows(odrNum, stepNum)) backPainter.fillRect(0, rowY, stepNumWidth_, stepFontHeight_, palette_->ptnHovCellColor); // Paint hover if (textChanged_) { textPainter.setPen(!(stepNum % hl2Cnt_) ? palette_->ptnHl2StepNumColor : !(stepNum % hl1Cnt_) ? palette_->ptnHl1StepNumColor : palette_->ptnDefStepNumColor); textPainter.drawText(1, baseY, QString("%1").arg(stepNum, stepNumWidthCnt_, stepNumBase_, QChar('0')).toUpper()); } // Step data for (int x = stepNumWidth_, trackVisIdx = leftTrackVisIdx_; x < maxWidth; ++trackVisIdx) { x += drawStep(forePainter, textPainter, backPainter, trackVisIdx, odrNum, stepNum, x, baseY, rowY); } if (foreChanged_) { if (odrNum != curPos_.order) // Mask forePainter.fillRect(0, rowY, maxWidth, stepFontHeight_, palette_->ptnMaskColor); } viewedFirstPos_.setRows(odrNum, stepNum); } int stepEnd = static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order)); /* Next rows */ viewedLastPos_ = curPos_; for (rowY = viewedCenterY_ + stepFontHeight_, baseY = viewedCenterBaseY_ + stepFontHeight_, stepNum = curPos_.step + 1, odrNum = curPos_.order; rowY < viewedRowsHeight_; rowY += stepFontHeight_, baseY += stepFontHeight_, ++stepNum) { if (stepNum == stepEnd) { if (odrNum == static_cast(bt_->getOrderSize(curSongNum_)) - 1) { break; } else if (config_->getShowPreviousNextOrders()) { ++odrNum; stepNum = 0; stepEnd = static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, odrNum)); } else { break; } } QColor rowColor; if (!config_->getFollowMode() && odrNum == playOdrNum && stepNum == playStepNum) { rowColor = palette_->ptnPlayStepColor; } else { rowColor = !(stepNum % hl2Cnt_) ? palette_->ptnHl2StepColor : !(stepNum % hl1Cnt_) ? palette_->ptnHl1StepColor : palette_->ptnDefStepColor; } // Fill row backPainter.fillRect(0, rowY, maxWidth, stepFontHeight_, rowColor); // Step number if (markerPos_.isEqualRows(odrNum, stepNum)) backPainter.fillRect(0, rowY, stepNumWidth_, stepFontHeight_, palette_->ptnMarkerColor); // Paint marker if (hovPos_.trackVisIdx == -2 && hovPos_.isEqualRows(odrNum, stepNum)) backPainter.fillRect(0, rowY, stepNumWidth_, stepFontHeight_, palette_->ptnHovCellColor); // Paint hover if (textChanged_) { textPainter.setPen(!(stepNum % hl2Cnt_) ? palette_->ptnHl2StepNumColor : !(stepNum % hl1Cnt_) ? palette_->ptnHl1StepNumColor : palette_->ptnDefStepNumColor); textPainter.drawText(1, baseY, QString("%1").arg(stepNum, stepNumWidthCnt_, stepNumBase_, QChar('0')).toUpper()); } // Step data for (int x = stepNumWidth_, trackVisIdx = leftTrackVisIdx_; x < maxWidth; ++trackVisIdx) { x += drawStep(forePainter, textPainter, backPainter, trackVisIdx, odrNum, stepNum, x, baseY, rowY); } if (foreChanged_) { if (odrNum != curPos_.order) // Mask forePainter.fillRect(0, rowY, maxWidth, stepFontHeight_, palette_->ptnMaskColor); } viewedLastPos_.setRows(odrNum, stepNum); } } void PatternEditorPanel::quickDrawRows(int maxWidth) { int ratio = iRatio(*this); int halfRowsCnt = viewedRowCnt_ >> 1; bool repaintForeAll = (curPos_.step - stepDownCount_ < 0); int shift = stepFontHeight_ * stepDownCount_; /* Move up */ { // QPixmap::scroll() takes physical pixels, not virtual. int phShift = shift * ratio; QRect srcRect(0, 0, maxWidth, viewedRowsHeight_); srcRect = scaleRect(srcRect, ratio); if (!repaintForeAll) forePixmap_.scroll(0, -phShift, srcRect); textPixmap_.scroll(0, -phShift, srcRect); backPixmap_.scroll(0, -phShift, srcRect); } { PatternPosition fpos = calculatePositionFrom(viewedCenterPos_.order, viewedCenterPos_.step, stepDownCount_ - halfRowsCnt); if (fpos.order != -1) viewedFirstPos_ = std::move(fpos); } QPainter forePainter(&forePixmap_); QPainter textPainter(&textPixmap_); QPainter backPainter(&backPixmap_); textPainter.setFont(stepFont_); /* Clear previous cursor row, current cursor row and last rows text and foreground */ int prevY = viewedCenterY_ - shift; int lastY = viewedRowsHeight_ - shift; textPainter.setCompositionMode(QPainter::CompositionMode_Source); textPainter.fillRect(0, prevY, maxWidth, stepFontHeight_, Qt::transparent); textPainter.fillRect(0, viewedCenterY_, maxWidth, stepFontHeight_, Qt::transparent); textPainter.fillRect(0, lastY, maxWidth, shift, Qt::transparent); textPainter.setCompositionMode(QPainter::CompositionMode_SourceOver); if (!repaintForeAll) { forePainter.setCompositionMode(QPainter::CompositionMode_Source); forePainter.fillRect(0, prevY, maxWidth, stepFontHeight_, Qt::transparent); forePainter.fillRect(0, viewedCenterY_, maxWidth, stepFontHeight_, Qt::transparent); forePainter.fillRect(0, lastY, maxWidth, shift, Qt::transparent); forePainter.setCompositionMode(QPainter::CompositionMode_SourceOver); } /* Redraw previous cursor step */ { int baseY = viewedCenterBaseY_ - shift; QColor rowColor = !(viewedCenterPos_.step % hl2Cnt_) ? palette_->ptnHl2StepColor : !(viewedCenterPos_.step % hl1Cnt_) ? palette_->ptnHl1StepColor : palette_->ptnDefStepColor; // Fill row backPainter.fillRect(0, prevY, maxWidth, stepFontHeight_, rowColor); // Step number if (markerPos_.isEqualRows(viewedCenterPos_)) backPainter.fillRect(0, prevY, stepNumWidth_, stepFontHeight_, palette_->ptnMarkerColor); // Paint marker if (hovPos_.trackVisIdx == -2 && hovPos_.isEqualRows(viewedCenterPos_)) backPainter.fillRect(0, prevY, stepNumWidth_, stepFontHeight_, palette_->ptnHovCellColor); // Paint hover textPainter.setPen(!(viewedCenterPos_.step % hl2Cnt_) ? palette_->ptnHl2StepNumColor : !(viewedCenterPos_.step % hl1Cnt_) ? palette_->ptnHl1StepNumColor : palette_->ptnDefStepNumColor); textPainter.drawText(1, baseY, QString("%1").arg(viewedCenterPos_.step, stepNumWidthCnt_, stepNumBase_, QChar('0')).toUpper()); // Step data for (int x = stepNumWidth_, trackVisIdx = leftTrackVisIdx_; x < maxWidth; ++trackVisIdx) { x += drawStep(forePainter, textPainter, backPainter, trackVisIdx, viewedCenterPos_.order, viewedCenterPos_.step, x, baseY, prevY); } } /* Redraw current cursor step */ // Fill row backPainter.fillRect(0, viewedCenterY_, maxWidth, stepFontHeight_, bt_->isJamMode() ? palette_->ptnCurStepColor : palette_->ptnCurEditStepColor); // Step number if (markerPos_.isEqualRows(curPos_)) backPainter.fillRect(0, viewedCenterY_, stepNumWidth_, stepFontHeight_, palette_->ptnMarkerColor); // Paint marker if (hovPos_.trackVisIdx == -2 && hovPos_.isEqualRows(curPos_)) backPainter.fillRect(0, viewedCenterY_, stepNumWidth_, stepFontHeight_, palette_->ptnHovCellColor); // Paint hover if (curPos_.step % hl2Cnt_) { textPainter.setPen(!(curPos_.step % hl2Cnt_) ? palette_->ptnHl1StepNumColor : !(curPos_.step % hl1Cnt_) ? palette_->ptnHl1StepNumColor : palette_->ptnDefStepNumColor); } else { textPainter.setPen(palette_->ptnHl2StepNumColor); } textPainter.drawText(1, viewedCenterBaseY_, QString("%1").arg(curPos_.step, stepNumWidthCnt_, stepNumBase_, QChar('0')).toUpper()); // Step data for (int x = stepNumWidth_, trackVisIdx = leftTrackVisIdx_; x < maxWidth; ++trackVisIdx) { x += drawStep(forePainter, textPainter, backPainter, trackVisIdx, curPos_.order, curPos_.step, x, viewedCenterBaseY_, viewedCenterY_); } viewedCenterPos_ = curPos_; /* Draw new step at last if necessary */ { PatternPosition bpos = calculatePositionFrom(viewedCenterPos_.order, viewedCenterPos_.step, halfRowsCnt); if (!config_->getShowPreviousNextOrders() && viewedCenterPos_.order != bpos.order) { // Clear row backPainter.setCompositionMode(QPainter::CompositionMode_Source); backPainter.fillRect(0, lastY, maxWidth, shift, Qt::transparent); } else { int baseY = lastY + (viewedCenterBaseY_ - viewedCenterY_); bpos = std::exchange(viewedLastPos_, bpos); while (true) { if (bpos.compareRows(viewedLastPos_) == 0) break; PatternPosition tmpBpos = calculatePositionFrom(bpos.order, bpos.step, 1); if (tmpBpos.order == -1) { // when viewedLastPos_.row == -1 (viewedlastPos_.row < viewedCenterPos_.row + halRowsCnt) viewedLastPos_ = bpos; // Clear row backPainter.setCompositionMode(QPainter::CompositionMode_Source); backPainter.fillRect(0, lastY, maxWidth, shift, Qt::transparent); break; } else { bpos = tmpBpos; } QColor rowColor = !(bpos.step % hl2Cnt_) ? palette_->ptnHl2StepColor : !(bpos.step % hl1Cnt_) ? palette_->ptnHl1StepColor : palette_->ptnDefStepColor; // Fill row backPainter.fillRect(0, lastY, maxWidth, stepFontHeight_, rowColor); // Step number if (markerPos_.isEqualRows(bpos)) backPainter.fillRect(0, lastY, stepNumWidth_, stepFontHeight_, palette_->ptnMarkerColor); // Paint marker if (hovPos_.trackVisIdx == -2 && hovPos_.isEqualRows(bpos)) backPainter.fillRect(0, lastY, stepNumWidth_, stepFontHeight_, palette_->ptnHovCellColor); // Paint hover textPainter.setPen(!(bpos.step % hl2Cnt_) ? palette_->ptnHl2StepNumColor : !(bpos.step % hl1Cnt_) ? palette_->ptnHl1StepNumColor : palette_->ptnDefStepNumColor); textPainter.drawText(1, baseY, QString("%1").arg(bpos.step, stepNumWidthCnt_, stepNumBase_, QChar('0')).toUpper()); // Step data for (int x = stepNumWidth_, trackVisIdx = leftTrackVisIdx_; x < maxWidth; ++trackVisIdx) { x += drawStep(forePainter, textPainter, backPainter, trackVisIdx, bpos.order, bpos.step, x, baseY, lastY); } if (bpos.order != curPos_.order) // Mask forePainter.fillRect(0, lastY, maxWidth, stepFontHeight_, palette_->ptnMaskColor); baseY += stepFontHeight_; lastY += stepFontHeight_; } } } /* Redraw foreground all area if new order */ if (repaintForeAll) { forePixmap_.fill(Qt::transparent); int y = viewedCenterY_ - viewedCenterPos_.step * stepFontHeight_; if (y > 0) forePainter.fillRect(0, 0, maxWidth, y, palette_->ptnMaskColor); if (viewedLastPos_.order != viewedCenterPos_.order) { y += static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, viewedCenterPos_.order)) * stepFontHeight_; forePainter.fillRect(0, y, maxWidth, viewedRowsHeight_ - y, palette_->ptnMaskColor); } for (int x = stepNumWidth_, trackVisIdx = leftTrackVisIdx_; x < maxWidth; ++trackVisIdx) { int w = baseTrackWidth_ + effWidth_ * rightEffn_.at(static_cast(trackVisIdx)); if (foreChanged_ && bt_->isMute(visTracks_.at(trackVisIdx))) // Paint mute mask forePainter.fillRect(x, 0, w, viewedRowsHeight_, palette_->ptnMaskColor); x += w; } } } int PatternEditorPanel::drawStep(QPainter &forePainter, QPainter &textPainter, QPainter& backPainter, int trackVisIdx, int orderNum, int stepNum, int x, int baseY, int rowY) { int trackNum = visTracks_.at(trackVisIdx); int offset = x + widthSpace_; PatternPosition pos{ trackVisIdx, 0, orderNum, stepNum }; QColor textColor = curPos_.isEqualRows(orderNum, stepNum) ? palette_->ptnCurTextColor : palette_->ptnDefTextColor; bool isHovTrack = (hovPos_.order == -2 && hovPos_.trackVisIdx == trackVisIdx); bool isHovStep = (hovPos_.trackVisIdx == -2 && hovPos_.isEqualRows(orderNum, stepNum)); SoundSource src = songStyle_.trackAttribs[static_cast(trackNum)].source; /* Tone name */ if (pos == curPos_) // Paint current cell backPainter.fillRect(offset - widthSpace_, rowY, toneNameWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnCurCellColor); if (pos == hovPos_ || isHovTrack || isHovStep) // Paint hover backPainter.fillRect(offset - widthSpace_, rowY, toneNameWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnHovCellColor); if ((selLeftAbovePos_.trackVisIdx >= 0 && selLeftAbovePos_.order >= 0) && isSelectedCell(trackVisIdx, 0, orderNum, stepNum)) // Paint selected backPainter.fillRect(offset - widthSpace_, rowY, toneNameWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnSelCellColor); if (textChanged_) { int noteNum = bt_->getStepNoteNumber(curSongNum_, trackNum, orderNum, stepNum); switch (noteNum) { case Step::NOTE_NONE: textPainter.setPen(textColor); textPainter.drawText(offset, baseY, "---"); break; case Step::NOTE_KEY_OFF: { int h = stepFontHeight_ / 7; textPainter.fillRect(offset, rowY + stepFontHeight_ * 2 / 7, toneNameWidth_, h, palette_->ptnNoteColor); textPainter.fillRect(offset, rowY + stepFontHeight_ * 4 / 7, toneNameWidth_, h, palette_->ptnNoteColor); break; } case Step::NOTE_KEY_CUT: textPainter.fillRect(offset, rowY + stepFontHeight_ * 2 / 5, toneNameWidth_, stepFontHeight_ / 5, palette_->ptnNoteColor); break; case Step::NOTE_ECHO0: textPainter.setPen(palette_->ptnNoteColor); textPainter.drawText(offset + stepFontWidth_ / 2, baseY, "^0"); break; case Step::NOTE_ECHO1: textPainter.setPen(palette_->ptnNoteColor); textPainter.drawText(offset + stepFontWidth_ / 2, baseY, "^1"); break; case Step::NOTE_ECHO2: textPainter.setPen(palette_->ptnNoteColor); textPainter.drawText(offset + stepFontWidth_ / 2, baseY, "^2"); break; case Step::NOTE_ECHO3: textPainter.setPen(palette_->ptnNoteColor); textPainter.drawText(offset + stepFontWidth_ / 2, baseY, "^3"); break; default: // Convert tone name { textPainter.setPen(palette_->ptnNoteColor); KeySignature::Type key = bt_->searchKeySignatureAt(curSongNum_, orderNum, stepNum); textPainter.drawText(offset, baseY, NoteNameManager::getManager().getNoteString(noteNum, key)); break; } } } offset += toneNameWidth_ + widthSpaceDbl_; pos.colInTrack = 1; /* Instrument */ if (pos == curPos_) // Paint current cell backPainter.fillRect(offset - widthSpace_, rowY, instWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnCurCellColor); if (pos == hovPos_ || isHovTrack || isHovStep) // Paint hover backPainter.fillRect(offset - widthSpace_, rowY, instWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnHovCellColor); if ((selLeftAbovePos_.trackVisIdx >= 0 && selLeftAbovePos_.order >= 0) && isSelectedCell(trackVisIdx, 1, orderNum, stepNum)) // Paint selected backPainter.fillRect(offset - widthSpace_, rowY, instWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnSelCellColor); if (textChanged_) { int instNum = bt_->getStepInstrument(curSongNum_, trackNum, orderNum, stepNum); if (instNum == -1) { textPainter.setPen(textColor); textPainter.drawText(offset, baseY, "--"); } else { std::unique_ptr inst = bt_->getInstrument(instNum); textPainter.setPen((inst != nullptr && src == inst->getSoundSource()) ? palette_->ptnInstColor : palette_->ptnErrorColor); textPainter.drawText(offset, baseY, QString("%1").arg(instNum, 2, 16, QChar('0')).toUpper()); } } offset += instWidth_ + widthSpaceDbl_; pos.colInTrack = 2; /* Volume */ if (pos == curPos_) // Paint current cell backPainter.fillRect(offset - widthSpace_, rowY, volWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnCurCellColor); if (pos == hovPos_ || isHovTrack || isHovStep) // Paint hover backPainter.fillRect(offset - widthSpace_, rowY, volWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnHovCellColor); if ((selLeftAbovePos_.trackVisIdx >= 0 && selLeftAbovePos_.order >= 0) && isSelectedCell(trackVisIdx, 2, orderNum, stepNum)) // Paint selected backPainter.fillRect(offset - widthSpace_, rowY, volWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnSelCellColor); if (textChanged_) { int vol = bt_->getStepVolume(curSongNum_, trackNum, orderNum, stepNum); if (vol == -1) { textPainter.setPen(textColor); textPainter.drawText(offset, baseY, "--"); } else { int volLim = 0; // Dummy set switch (src) { case SoundSource::FM: volLim = bt_defs::NSTEP_FM_VOLUME ; break; case SoundSource::SSG: volLim = bt_defs::NSTEP_SSG_VOLUME; break; case SoundSource::RHYTHM: volLim = bt_defs::NSTEP_RHYTHM_VOLUME; break; case SoundSource::ADPCM: volLim = bt_defs::NSTEP_ADPCM_VOLUME; break; } textPainter.setPen((vol < volLim) ? palette_->ptnVolColor : palette_->ptnErrorColor); if (src == SoundSource::FM && vol < volLim && config_->getReverseFMVolumeOrder()) { vol = volLim - vol - 1; } textPainter.drawText(offset, baseY, QString("%1").arg(vol, 2, 16, QChar('0')).toUpper()); } } offset += volWidth_ + widthSpaceDbl_; pos.colInTrack = 3; /* Effect */ for (int i = 0; i <= rightEffn_.at(static_cast(trackVisIdx)); ++i) { /* Effect ID */ if (pos == curPos_) // Paint current cell backPainter.fillRect(offset - widthSpace_, rowY, effIDWidth_ + widthSpace_, stepFontHeight_, palette_->ptnCurCellColor); if (pos == hovPos_ || isHovTrack || isHovStep) // Paint hover backPainter.fillRect(offset - widthSpace_, rowY, effIDWidth_ + widthSpace_, stepFontHeight_, palette_->ptnHovCellColor); if ((selLeftAbovePos_.trackVisIdx >= 0 && selLeftAbovePos_.order >= 0) && isSelectedCell(trackVisIdx, pos.colInTrack, orderNum, stepNum)) // Paint selected backPainter.fillRect(offset - widthSpace_, rowY, effIDWidth_ + widthSpace_, stepFontHeight_, palette_->ptnSelCellColor); std::string effId; QString effStr; if (textChanged_) { effId = bt_->getStepEffectID(curSongNum_, trackNum, orderNum, stepNum, i); effStr = QString::fromStdString(effId); if (effStr == "--") { textPainter.setPen(textColor); textPainter.drawText(offset, baseY, effStr); } else { textPainter.setPen(palette_->ptnEffColor); textPainter.drawText(offset, baseY, effStr); } } offset += effIDWidth_; ++pos.colInTrack; /* Effect Value */ if (pos == curPos_) // Paint current cell backPainter.fillRect(offset, rowY, effValWidth_ + widthSpace_, stepFontHeight_, palette_->ptnCurCellColor); if (pos == hovPos_ || isHovTrack || isHovStep) // Paint hover backPainter.fillRect(offset, rowY, effValWidth_ + widthSpace_, stepFontHeight_, palette_->ptnHovCellColor); if ((selLeftAbovePos_.trackVisIdx >= 0 && selLeftAbovePos_.order >= 0) && isSelectedCell(trackVisIdx, pos.colInTrack, orderNum, stepNum)) // Paint selected backPainter.fillRect(offset, rowY, effValWidth_ + widthSpace_, stepFontHeight_, palette_->ptnSelCellColor); if (textChanged_) { int effVal = bt_->getStepEffectValue(curSongNum_, trackNum, orderNum, stepNum, i); if (effVal == -1) { textPainter.setPen(textColor); textPainter.drawText(offset, baseY, "--"); } else { textPainter.setPen(palette_->ptnEffColor); switch (effect_utils::validateEffectId(src, effId)) { case EffectType::VolumeDelay: if (src == SoundSource::FM && config_->getReverseFMVolumeOrder()) effVal = effect_utils::reverseFmVolume(effVal); break; case EffectType::Brightness: if (config_->getReverseFMVolumeOrder()) effVal = effect_utils::reverseFmBrightness(effVal); break; default: break; } textPainter.drawText(offset, baseY, QString("%1").arg(effVal, 2, 16, QChar('0')).toUpper()); } } offset += effValWidth_ + widthSpaceDbl_; ++pos.colInTrack; } if (foreChanged_ && bt_->isMute(trackNum)) // Paint mute mask forePainter.fillRect(x, rowY, offset - x - 1, stepFontHeight_, palette_->ptnMaskColor); return baseTrackWidth_ + effWidth_ * rightEffn_[static_cast(trackVisIdx)]; } void PatternEditorPanel::drawHeaders(int maxWidth) { QPainter painter(&headerPixmap_); painter.setFont(headerFont_); painter.fillRect(0, 0, geometry().width(), headerHeight_, palette_->ptnHeaderRowColor); painter.setPen(palette_->ptnHeaderBorderColor); qreal bottomLineY = headerHeight_ - 0.5; painter.drawLine(QPointF(0., bottomLineY), QPointF(geometry().width(), bottomLineY)); int lspace = stepFontWidth_ / 2; for (int x = stepNumWidth_, trackVisIdx = leftTrackVisIdx_; x < maxWidth; ++trackVisIdx) { painter.setPen(palette_->ptnHeaderBorderColor); painter.drawLine(x, 0, x, headerHeight_); int trackNum = visTracks_.at(trackVisIdx); int tw = baseTrackWidth_ + effWidth_ * rightEffn_.at(static_cast(trackVisIdx)); if (hovPos_.order == -2 && hovPos_.trackVisIdx == trackVisIdx) painter.fillRect(x, 0, tw, headerHeight_, palette_->ptnHovCellColor); int left = x + lspace; painter.setPen(palette_->ptnHeaderTextColor); const TrackAttribute& attrib = songStyle_.trackAttribs[static_cast(trackNum)]; painter.drawText(left, headerFontAscent_, gui_utils::getTrackName(songStyle_.type, attrib.source, attrib.channelInSource)); painter.fillRect(left, headerHeight_ - 4, hdMuteToggleWidth_, 2, bt_->isMute(trackNum) ? palette_->ptnMuteColor : palette_->ptnUnmuteColor); painter.drawText(left + hdMuteToggleWidth_ + lspace, hdPlusY_, "+"); painter.drawText(left + hdMuteToggleWidth_ + lspace, hdMinusY_, "-"); x += tw; } } void PatternEditorPanel::drawBorders(int maxWidth) { QPainter painter(&backPixmap_); painter.setPen(palette_->ptnBorderColor); painter.drawLine(stepNumWidth_, 0, stepNumWidth_, backPixmap_.height()); size_t trackVisIdx = static_cast(leftTrackVisIdx_); for (int x = stepNumWidth_; trackVisIdx < rightEffn_.size(); ) { x += (baseTrackWidth_ + effWidth_ * rightEffn_.at(trackVisIdx)); if (x > maxWidth) break; painter.drawLine(x, 0, x, backPixmap_.height()); ++trackVisIdx; } } void PatternEditorPanel::drawShadow() { QPainter painter(&completePixmap_); painter.fillRect(0, 0, geometry().width(), geometry().height(), palette_->ptnUnfocusedShadowColor); } // NOTE: end >= -1 int PatternEditorPanel::calculateTracksWidthWithRowNum(int beginIdx, int endIdx) const { int width = stepNumWidth_; for (int i = beginIdx; i <= endIdx; ++i) { width += (baseTrackWidth_ + effWidth_ * rightEffn_.at(static_cast(i))); } return width; } int PatternEditorPanel::calculateColNumInRow(int trackVisIdx, int colNumInTrack, bool isExpanded) const { if (isExpanded) { return trackVisIdx * 11 + colNumInTrack; } else { trackVisIdx = std::min(trackVisIdx, static_cast(rightEffn_.size())); return std::accumulate(rightEffn_.begin(), rightEffn_.begin() + trackVisIdx, colNumInTrack, [](int acc, int v) { return acc + 5 + 2 * v; }); } } void PatternEditorPanel::moveCursorToRight(int n) { int oldTrackIdx = curPos_.trackVisIdx; int oldLeftTrackIdx = leftTrackVisIdx_; curPos_.colInTrack += n; if (n > 0) { while (true) { int lim = 5 + 2 * rightEffn_.at(static_cast(curPos_.trackVisIdx)); if (curPos_.colInTrack < lim) { break; } else { if (curPos_.trackVisIdx == static_cast(visTracks_.size()) - 1) { if (config_->getWarpCursor()) { curPos_.trackVisIdx = 0; } else { curPos_.colInTrack = lim - 1; break; } } else { ++curPos_.trackVisIdx; } curPos_.colInTrack -= lim; } } } else { while (true) { if (curPos_.colInTrack >= 0) { break; } else { if (!curPos_.trackVisIdx) { if (config_->getWarpCursor()) { curPos_.trackVisIdx = static_cast(visTracks_.size()) - 1; } else { curPos_.colInTrack = 0; break; } } else { --curPos_.trackVisIdx; } curPos_.colInTrack += (5 + 2 * rightEffn_.at(static_cast(curPos_.trackVisIdx))); } } } if (oldTrackIdx < curPos_.trackVisIdx) { while (calculateTracksWidthWithRowNum(leftTrackVisIdx_, curPos_.trackVisIdx) > geometry().width()) ++leftTrackVisIdx_; } else { if (curPos_.trackVisIdx < leftTrackVisIdx_) leftTrackVisIdx_ = curPos_.trackVisIdx; } updateTracksWidthFromLeftToEnd(); entryCnt_ = 0; if (curPos_.trackVisIdx != oldTrackIdx) bt_->setCurrentTrack(visTracks_.at(curPos_.trackVisIdx)); if (!isIgnoreToSlider_) { if (config_->getMoveCursorByHorizontalScroll()) { emit hScrollBarChangeRequested(calculateColNumInRow(curPos_.trackVisIdx, curPos_.colInTrack)); } else if (curPos_.trackVisIdx != oldTrackIdx) { emit hScrollBarChangeRequested(leftTrackVisIdx_); } } if (!isIgnoreToOrder_ && curPos_.trackVisIdx != oldTrackIdx) // Send to order list emit currentTrackChanged(curPos_.trackVisIdx); // Request fore-background repaint if leftmost track is changed else request only background repaint if (leftTrackVisIdx_ != oldLeftTrackIdx) { headerChanged_ = true; foreChanged_ = true; textChanged_ = true; } backChanged_ = true; repaint(); } void PatternEditorPanel::moveViewToRight(int n) { leftTrackVisIdx_ += n; updateTracksWidthFromLeftToEnd(); // Calculate cursor position int trackIdx = curPos_.trackVisIdx + n; int col = std::min(curPos_.colInTrack, 4 + 2 * rightEffn_.at(static_cast(trackIdx))); // Check visible int width = stepNumWidth_; for (int i = leftTrackVisIdx_; i <= trackIdx; ++i) { width += (baseTrackWidth_ + effWidth_ * rightEffn_.at(static_cast(i))); if (geometry().width() < width) { trackIdx = i - 1; col = 4 + 2 * rightEffn_.at(static_cast(trackIdx)); break; } } // Move cursor and repaint all headerChanged_ = true; foreChanged_ = true; textChanged_ = true; moveCursorToRight(calculateColumnDistance(curPos_.trackVisIdx, curPos_.colInTrack, trackIdx, col)); } void PatternEditorPanel::moveCursorToDown(int n) { int oldOdr = curPos_.order; int tmp = curPos_.step + n; if (n > 0) { while (true) { int dif = tmp - static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order)); if (dif < 0) { curPos_.step = tmp; break; } else { if (config_->getWarpAcrossOrders()) { if (curPos_.order == static_cast(bt_->getOrderSize(curSongNum_)) - 1) { curPos_.order = 0; } else { ++curPos_.order; } } tmp = dif; } } } else { while (true) { if (tmp < 0) { if (config_->getWarpAcrossOrders()) { if (curPos_.order == 0) { curPos_.order = static_cast(bt_->getOrderSize(curSongNum_)) - 1; } else { --curPos_.order; } } tmp += bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order); } else { curPos_.step = tmp; break; } } } if (curPos_.order != oldOdr) bt_->setCurrentOrderNumber(curPos_.order); bt_->setCurrentStepNumber(curPos_.step); entryCnt_ = 0; if (!isIgnoreToSlider_) emit vScrollBarChangeRequested( curPos_.step, static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order)) - 1); if (!isIgnoreToOrder_ && curPos_.order != oldOdr) // Send to order list emit currentOrderChanged( curPos_.order, static_cast(bt_->getOrderSize(curSongNum_)) - 1); backChanged_ = true; textChanged_ = true; foreChanged_ = true; repaint(); } int PatternEditorPanel::calculateColumnDistance(int beginTrackIdx, int beginColumn, int endTrackIdx, int endColumn, bool isExpanded) const { return (calculateColNumInRow(endTrackIdx, endColumn, isExpanded) - calculateColNumInRow(beginTrackIdx, beginColumn, isExpanded)); } int PatternEditorPanel::calculateStepDistance(int beginOrder, int beginStep, int endOrder, int endStep) const { int d = 0; int startOrder, startStep, stopOrder, stopStep; bool flag; if (endOrder >= beginOrder) { startOrder = endOrder; startStep = endStep; stopOrder = beginOrder; stopStep = beginStep; flag = true; } else { startOrder = beginOrder; startStep = beginStep; stopOrder = endOrder; stopStep = endStep; flag = false; } while (true) { if (startOrder == stopOrder) { d += (startStep - stopStep); break; } else { d += startStep; startStep = static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, --startOrder)); } } return flag ? d : -d; } PatternPosition PatternEditorPanel::calculatePositionFrom(int order, int step, int by) const { PatternPosition pos{ -1, -1, order, step + by }; if (by > 0) { while (true) { int dif = pos.step - static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, pos.order)); if (dif < 0) { break; } else { if (pos.order == static_cast(bt_->getOrderSize(curSongNum_)) - 1) { return { -1, -1, -1, -1 }; } else { ++pos.order; } pos.step = dif; } } } else { while (true) { if (pos.step < 0) { if (pos.order == 0) { return { -1, -1, -1, -1 }; } else { --pos.order; } pos.step += bt_->getPatternSizeFromOrderNumber(curSongNum_, pos.order); } else { break; } } } return pos; } QPoint PatternEditorPanel::calculateCurrentCursorPosition() const { int w = calculateTracksWidthWithRowNum(leftTrackVisIdx_, curPos_.trackVisIdx - 1); if (curPos_.colInTrack > 0) { w = w + toneNameWidth_ + widthSpaceDbl_; if (curPos_.colInTrack > 1) { w = w + instWidth_ + widthSpaceDbl_; if (curPos_.colInTrack > 2) { w = w + volWidth_ + widthSpaceDbl_; for (int i = 3; i < 11; ++i) { if (curPos_.colInTrack == i) break; w = w + widthSpace_ + ((i % 2) ? effIDWidth_ : effValWidth_); } } } } return QPoint(w, curRowY_); } int PatternEditorPanel::getScrollableCountByTrack() const { int width = stepNumWidth_; size_t i = visTracks_.size(); do { --i; width += (baseTrackWidth_ + effWidth_ * rightEffn_.at(i)); if (geometry().width() < width) { return static_cast(i + 1); } } while (i); return 0; } void PatternEditorPanel::changeEditable() { backChanged_ = true; repaint(); } int PatternEditorPanel::getFullColmunSize() const { return calculateColNumInRow(static_cast(visTracks_.size()) - 1, 4 + 2 * rightEffn_.back()); } void PatternEditorPanel::updatePositionByStepUpdate(bool isFirstUpdate, bool forceJump, bool trackChanged) { if (!forceJump && !config_->getFollowMode()) { // Repaint only background backChanged_ = true; repaint(); return; } if (trackChanged) { int trackVisIdx = std::distance(visTracks_.begin(), utils::find(visTracks_, bt_->getCurrentTrackAttribute().number)); int oldTrackVisIdx = std::exchange(curPos_.trackVisIdx, trackVisIdx); curPos_.colInTrack = 0; if (oldTrackVisIdx < curPos_.trackVisIdx) { while (calculateTracksWidthWithRowNum(leftTrackVisIdx_, curPos_.trackVisIdx) > geometry().width()) { ++leftTrackVisIdx_; headerChanged_ = true; } } else { if (curPos_.trackVisIdx < leftTrackVisIdx_) { leftTrackVisIdx_ = curPos_.trackVisIdx; headerChanged_ = true; } } updateTracksWidthFromLeftToEnd(); if (!isIgnoreToSlider_) { if (config_->getMoveCursorByHorizontalScroll()) emit hScrollBarChangeRequested(calculateColNumInRow(curPos_.trackVisIdx, curPos_.colInTrack)); else emit hScrollBarChangeRequested(leftTrackVisIdx_); } } PatternPosition tmp = curPos_; curPos_.setRows(bt_->getCurrentOrderNumber(), bt_->getCurrentStepNumber()); int cmp = curPos_.compareRows(tmp); if (cmp || isFirstUpdate) { emit vScrollBarChangeRequested( curPos_.step, static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order)) - 1); if (isFirstUpdate || (cmp < 0) || (cmp && !config_->getShowPreviousNextOrders())) { stepDownCount_ = 0; // Redraw entire area in first update } else { int d = calculateStepDistance(tmp.order, tmp.step, curPos_.order, curPos_.step); stepDownCount_ = (d < (viewedRowCnt_ >> 1)) ? d : 0; } } else if (!trackChanged) return; // Delayed call, already updated. entryCnt_ = 0; // If stepChanged is false, repaint all pattern foreChanged_ = true; textChanged_ = true; backChanged_ = true; repaint(); } void PatternEditorPanel::changeMarker() { markerPos_.setRows(bt_->getMarkerOrder(), bt_->getMarkerStep()); backChanged_ = true; repaint(); } bool PatternEditorPanel::enterToneData(QKeyEvent* event) { int baseOct = bt_->getCurrentOctave(); auto modifiers = event->modifiers(); if (modifiers.testFlag(Qt::NoModifier) || modifiers.testFlag(Qt::KeypadModifier)) { Qt::Key qtKey = static_cast(event->key()); try { JamKey possibleJamKey = getJamKeyFromLayoutMapping(qtKey, config_); setStepKeyOn(jam_utils::makeNote(baseOct, possibleJamKey)); } catch (std::invalid_argument &) {} } return false; } void PatternEditorPanel::setStepKeyOn(const Note& note) { bt_->setStepNote(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.order, curPos_.step, note, config_->getInstrumentMask(), config_->getVolumeMask()); comStack_.lock()->push(new SetKeyOnToStepQtCommand(this)); if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(editableStepCnt_); } bool PatternEditorPanel::enterInstrumentData(int key) { switch (key) { case Qt::Key_0: setStepInstrument(0x0); return true; case Qt::Key_1: setStepInstrument(0x1); return true; case Qt::Key_2: setStepInstrument(0x2); return true; case Qt::Key_3: setStepInstrument(0x3); return true; case Qt::Key_4: setStepInstrument(0x4); return true; case Qt::Key_5: setStepInstrument(0x5); return true; case Qt::Key_6: setStepInstrument(0x6); return true; case Qt::Key_7: setStepInstrument(0x7); return true; case Qt::Key_8: setStepInstrument(0x8); return true; case Qt::Key_9: setStepInstrument(0x9); return true; case Qt::Key_A: setStepInstrument(0xa); return true; case Qt::Key_B: setStepInstrument(0xb); return true; case Qt::Key_C: setStepInstrument(0xc); return true; case Qt::Key_D: setStepInstrument(0xd); return true; case Qt::Key_E: setStepInstrument(0xe); return true; case Qt::Key_F: setStepInstrument(0xf); return true; default: return false; } } void PatternEditorPanel::setStepInstrument(int num) { int trackNum = visTracks_.at(curPos_.trackVisIdx); bt_->setStepInstrumentDigit(curSongNum_, trackNum, curPos_.order, curPos_.step, num, (entryCnt_ == 1)); comStack_.lock()->push(new SetInstrumentToStepQtCommand(this, curPos_, (entryCnt_ == 1))); emit instrumentEntered( bt_->getStepInstrument(curSongNum_, trackNum, curPos_.order, curPos_.step)); if ((!bt_->isPlaySong() || !bt_->isFollowPlay()) && !updateEntryCount()) moveCursorToDown(editableStepCnt_); } bool PatternEditorPanel::enterVolumeData(int key) { switch (key) { case Qt::Key_0: setStepVolume(0x0); return true; case Qt::Key_1: setStepVolume(0x1); return true; case Qt::Key_2: setStepVolume(0x2); return true; case Qt::Key_3: setStepVolume(0x3); return true; case Qt::Key_4: setStepVolume(0x4); return true; case Qt::Key_5: setStepVolume(0x5); return true; case Qt::Key_6: setStepVolume(0x6); return true; case Qt::Key_7: setStepVolume(0x7); return true; case Qt::Key_8: setStepVolume(0x8); return true; case Qt::Key_9: setStepVolume(0x9); return true; case Qt::Key_A: setStepVolume(0xa); return true; case Qt::Key_B: setStepVolume(0xb); return true; case Qt::Key_C: setStepVolume(0xc); return true; case Qt::Key_D: setStepVolume(0xd); return true; case Qt::Key_E: setStepVolume(0xe); return true; case Qt::Key_F: setStepVolume(0xf); return true; default: return false; } } void PatternEditorPanel::setStepVolume(int volume) { int vol = bt_->setStepVolumeDigit(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.order, curPos_.step, volume, (entryCnt_ == 1)); comStack_.lock()->push(new SetVolumeToStepQtCommand(this, curPos_, (entryCnt_ == 1))); if ((!bt_->isPlaySong() || !bt_->isFollowPlay()) && !updateEntryCount()) moveCursorToDown(editableStepCnt_); emit volumeEntered(vol); } bool PatternEditorPanel::enterEffectID(int key) { switch (key) { case Qt::Key_0: setStepEffectID("0"); return true; case Qt::Key_1: setStepEffectID("1"); return true; case Qt::Key_2: setStepEffectID("2"); return true; case Qt::Key_3: setStepEffectID("3"); return true; case Qt::Key_4: setStepEffectID("4"); return true; case Qt::Key_5: setStepEffectID("5"); return true; case Qt::Key_6: setStepEffectID("6"); return true; case Qt::Key_7: setStepEffectID("7"); return true; case Qt::Key_8: setStepEffectID("8"); return true; case Qt::Key_9: setStepEffectID("9"); return true; case Qt::Key_A: setStepEffectID("A"); return true; case Qt::Key_B: setStepEffectID("B"); return true; case Qt::Key_C: setStepEffectID("C"); return true; case Qt::Key_D: setStepEffectID("D"); return true; case Qt::Key_E: setStepEffectID("E"); return true; case Qt::Key_F: setStepEffectID("F"); return true; case Qt::Key_G: setStepEffectID("G"); return true; case Qt::Key_H: setStepEffectID("H"); return true; case Qt::Key_I: setStepEffectID("I"); return true; case Qt::Key_J: setStepEffectID("J"); return true; case Qt::Key_K: setStepEffectID("K"); return true; case Qt::Key_L: setStepEffectID("L"); return true; case Qt::Key_M: setStepEffectID("M"); return true; case Qt::Key_N: setStepEffectID("N"); return true; case Qt::Key_O: setStepEffectID("O"); return true; case Qt::Key_P: setStepEffectID("P"); return true; case Qt::Key_Q: setStepEffectID("Q"); return true; case Qt::Key_R: setStepEffectID("R"); return true; case Qt::Key_S: setStepEffectID("S"); return true; case Qt::Key_T: setStepEffectID("T"); return true; case Qt::Key_U: setStepEffectID("U"); return true; case Qt::Key_V: setStepEffectID("V"); return true; case Qt::Key_W: setStepEffectID("W"); return true; case Qt::Key_X: setStepEffectID("X"); return true; case Qt::Key_Y: setStepEffectID("Y"); return true; case Qt::Key_Z: setStepEffectID("Z"); return true; default: return false; } } void PatternEditorPanel::setStepEffectID(QString str) { int curTrackNum = visTracks_.at(curPos_.trackVisIdx); bt_->setStepEffectIDCharacter(curSongNum_, curTrackNum, curPos_.order, curPos_.step, (curPos_.colInTrack - 3) / 2, str.toStdString(), config_->getFill00ToEffectValue(), (entryCnt_ == 1)); comStack_.lock()->push(new SetEffectIDToStepQtCommand(this, curPos_, (entryCnt_ == 1))); PatternPosition editPos = curPos_; if ((!bt_->isPlaySong() || !bt_->isFollowPlay()) && !updateEntryCount()) { if (config_->getMoveCursorToRight()) moveCursorToRight(1); else moveCursorToDown(editableStepCnt_); } // Send effect description std::string id = bt_->getStepEffectID(curSongNum_, visTracks_.at(editPos.trackVisIdx), editPos.order, editPos.step, (editPos.colInTrack - 3) / 2); SoundSource src = songStyle_.trackAttribs.at(static_cast(curTrackNum)).source; emit effectEntered(effect_desc::getEffectFormatAndDetailString(effect_utils::validateEffectId(src, id))); } bool PatternEditorPanel::enterEffectValue(int key) { switch (key) { case Qt::Key_0: setStepEffectValue(0x0); return true; case Qt::Key_1: setStepEffectValue(0x1); return true; case Qt::Key_2: setStepEffectValue(0x2); return true; case Qt::Key_3: setStepEffectValue(0x3); return true; case Qt::Key_4: setStepEffectValue(0x4); return true; case Qt::Key_5: setStepEffectValue(0x5); return true; case Qt::Key_6: setStepEffectValue(0x6); return true; case Qt::Key_7: setStepEffectValue(0x7); return true; case Qt::Key_8: setStepEffectValue(0x8); return true; case Qt::Key_9: setStepEffectValue(0x9); return true; case Qt::Key_A: setStepEffectValue(0xa); return true; case Qt::Key_B: setStepEffectValue(0xb); return true; case Qt::Key_C: setStepEffectValue(0xc); return true; case Qt::Key_D: setStepEffectValue(0xd); return true; case Qt::Key_E: setStepEffectValue(0xe); return true; case Qt::Key_F: setStepEffectValue(0xf); return true; default: return false; } } void PatternEditorPanel::setStepEffectValue(int value) { int trackNum = visTracks_.at(curPos_.trackVisIdx); int n = (curPos_.colInTrack - 4) / 2; EffectDisplayControl ctrl = EffectDisplayControl::Unset; SoundSource src = songStyle_.trackAttribs[static_cast(trackNum)].source; switch (effect_utils::validateEffectId( src, bt_->getStepEffectID(curSongNum_, trackNum, curPos_.order, curPos_.step, n))) { case EffectType::VolumeDelay: if (src == SoundSource::FM && config_->getReverseFMVolumeOrder()) ctrl = EffectDisplayControl::ReverseFMVolumeDelay; break; case EffectType::Brightness: if (config_->getReverseFMVolumeOrder()) ctrl = EffectDisplayControl::ReverseFMBrightness; break; default: break; } bt_->setStepEffectValueDigit(curSongNum_, trackNum, curPos_.order, curPos_.step, n, value, ctrl, (entryCnt_ == 1)); comStack_.lock()->push(new SetEffectValueToStepQtCommand(this, curPos_, (entryCnt_ == 1))); if ((!bt_->isPlaySong() || !bt_->isFollowPlay()) && !updateEntryCount()) { if (config_->getMoveCursorToRight()) moveCursorToRight(1); else moveCursorToDown(editableStepCnt_); } } void PatternEditorPanel::insertStep() { bt_->insertStep(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.order, curPos_.step); comStack_.lock()->push(new InsertStepQtCommand(this)); } void PatternEditorPanel::deletePreviousStep() { if (curPos_.step) { bt_->deletePreviousStep(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.order, curPos_.step); comStack_.lock()->push(new DeletePreviousStepQtCommand(this)); if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(-1); } } void PatternEditorPanel::copySelectedCells() { if (selLeftAbovePos_.order == -1) return; int r = visTracks_.at(selRightBelowPos_.trackVisIdx) * 11 + selRightBelowPos_.colInTrack; int l = visTracks_.at(selLeftAbovePos_.trackVisIdx) * 11 + selLeftAbovePos_.colInTrack; int w = 1 + r - l; // Real selected region width int h = 1 + calculateStepDistance(selLeftAbovePos_.order, selLeftAbovePos_.step, selRightBelowPos_.order, selRightBelowPos_.step); QString str = QString("PATTERN_COPY:%1,%2,%3,").arg(selLeftAbovePos_.colInTrack).arg(w).arg(h); // NOTE: pos.trackVisIdx, endPos.trackVisIdx indecates a real track number. PatternPosition pos = { visTracks_.at(selLeftAbovePos_.trackVisIdx), selLeftAbovePos_.colInTrack, selLeftAbovePos_.order, selLeftAbovePos_.step }; const PatternPosition endPos = { visTracks_.at(selRightBelowPos_.trackVisIdx), selRightBelowPos_.colInTrack, selRightBelowPos_.order, selRightBelowPos_.step }; while (true) { switch (pos.colInTrack) { case 0: str += QString::number(bt_->getStepNoteNumber(curSongNum_, pos.trackVisIdx, pos.order, pos.step)); break; case 1: str += QString::number(bt_->getStepInstrument(curSongNum_, pos.trackVisIdx, pos.order, pos.step)); break; case 2: str += QString::number(bt_->getStepVolume(curSongNum_, pos.trackVisIdx, pos.order, pos.step)); break; case 3: str += QString::fromStdString(bt_->getStepEffectID(curSongNum_, pos.trackVisIdx, pos.order, pos.step, 0)); break; case 4: str += QString::number(bt_->getStepEffectValue(curSongNum_, pos.trackVisIdx, pos.order, pos.step, 0)); break; case 5: str += QString::fromStdString(bt_->getStepEffectID(curSongNum_, pos.trackVisIdx, pos.order, pos.step, 1)); break; case 6: str += QString::number(bt_->getStepEffectValue(curSongNum_, pos.trackVisIdx, pos.order, pos.step, 1)); break; case 7: str += QString::fromStdString(bt_->getStepEffectID(curSongNum_, pos.trackVisIdx, pos.order, pos.step, 2)); break; case 8: str += QString::number(bt_->getStepEffectValue(curSongNum_, pos.trackVisIdx, pos.order, pos.step, 2)); break; case 9: str += QString::fromStdString(bt_->getStepEffectID(curSongNum_, pos.trackVisIdx, pos.order, pos.step, 3)); break; case 10: str += QString::number(bt_->getStepEffectValue(curSongNum_, pos.trackVisIdx, pos.order, pos.step, 3)); break; } if (pos.isEqualCols(endPos)) { if (pos.isEqualRows(endPos)) { break; } else { pos.setCols(visTracks_.at(selLeftAbovePos_.trackVisIdx), selLeftAbovePos_.colInTrack); ++pos.step; } } else { ++pos.colInTrack; pos.trackVisIdx += (pos.colInTrack / 11); pos.colInTrack %= 11; } str += ","; } QApplication::clipboard()->setText(str); } void PatternEditorPanel::eraseSelectedCells() { if (!bt_->erasePatternCells(curSongNum_, visTracks_.at(selLeftAbovePos_.trackVisIdx), selLeftAbovePos_.colInTrack, selLeftAbovePos_.order, selLeftAbovePos_.step, visTracks_.at(selRightBelowPos_.trackVisIdx), selRightBelowPos_.colInTrack, selRightBelowPos_.step)) { command_result_message_box::showCommandInvokingErrorMessageBox(window()); } comStack_.lock()->push(new EraseCellsInPatternQtCommand(this)); } namespace { std::optional >> decodeTextForPatternPasting(const QString& decodedText) { static const QRegularExpression COMMAND_REGEX { R"(^PATTERN_(COPY|CUT):(?\d+),(?\d+),(?\d+),(?.+)$)" }; const auto match = COMMAND_REGEX.match(decodedText); if (!match.hasMatch()) return std::nullopt; bool isOk{}; int startCol = match.captured("startCol").toInt(&isOk); if (!isOk || startCol < 0 || Step::N_COLUMN <= startCol) return std::nullopt; std::size_t w = match.captured("width").toUInt(); std::size_t h = match.captured("height").toUInt(); if (w == 0 || h == 0) return std::nullopt; QStringList data = match.captured("data").split(","); auto unmodifiedSize = data.size(); data.removeAll(""); if (static_cast(data.size()) != w * h || data.size() != unmodifiedSize) { return std::nullopt; } Vector2d cells(h, w); for (std::size_t i = 0; i < h; ++i) { for (std::size_t j = 0; j < w; ++j) { cells[i][j] = data[i * w + j].toStdString(); } } return std::make_pair(startCol, std::move(cells)); } } void PatternEditorPanel::pasteCopiedCells(const PatternPosition& cursorPos) { bool result = [&] { const auto decoded = decodeTextForPatternPasting(QApplication::clipboard()->text()); if (!decoded.has_value()) return false; auto [sCol, cells] = decoded.value(); PatternPosition pos = getPasteLeftAbovePosition(sCol, cursorPos, cells.columnSize()); if (config_->getPasteMode() == Configuration::PasteMode::Fill && selLeftAbovePos_.order != -1) { cells = makeCopiedCellsForPasteFull(pos, cells); } bool overflow = config_->getOverflowPaste(); if (!bt_->pastePatternCells( curSongNum_, visTracks_.at(pos.trackVisIdx), pos.colInTrack, pos.order, pos.step, cells, overflow)) { return false; } comStack_.lock()->push(new PasteCopiedDataToPatternQtCommand(this)); return true; }(); if (!result) command_result_message_box::showCommandInvokingErrorMessageBox(window()); } void PatternEditorPanel::pasteMixCopiedCells(const PatternPosition& cursorPos) { bool result = [&] { const auto decoded = decodeTextForPatternPasting(QApplication::clipboard()->text()); if (!decoded.has_value()) return false; auto [sCol, cells] = decoded.value(); PatternPosition pos = getPasteLeftAbovePosition(sCol, cursorPos, cells.columnSize()); if (config_->getPasteMode() == Configuration::PasteMode::Fill && selLeftAbovePos_.order != -1) { cells = makeCopiedCellsForPasteFull(pos, cells); } bool overflow = config_->getOverflowPaste(); if (!bt_->pasteMixPatternCells( curSongNum_, visTracks_.at(pos.trackVisIdx), pos.colInTrack, pos.order, pos.step, cells, overflow)) { return false; } comStack_.lock()->push(new PasteMixCopiedDataToPatternQtCommand(this)); return true; }(); if (!result) command_result_message_box::showCommandInvokingErrorMessageBox(window()); } void PatternEditorPanel::pasteOverwriteCopiedCells(const PatternPosition& cursorPos) { bool result = [&] { const auto decoded = decodeTextForPatternPasting(QApplication::clipboard()->text()); if (!decoded.has_value()) return false; auto [sCol, cells] = decoded.value(); PatternPosition pos = getPasteLeftAbovePosition(sCol, cursorPos, cells.columnSize()); if (config_->getPasteMode() == Configuration::PasteMode::Fill && selLeftAbovePos_.order != -1) { cells = makeCopiedCellsForPasteFull(pos, cells); } bool overflow = config_->getOverflowPaste(); if (!bt_->pasteOverwritePatternCells( curSongNum_, visTracks_.at(pos.trackVisIdx), pos.colInTrack, pos.order, pos.step, cells, overflow)) { return false; } comStack_.lock()->push(new PasteOverwriteCopiedDataToPatternQtCommand(this)); return true; }(); if (!result) command_result_message_box::showCommandInvokingErrorMessageBox(window()); } void PatternEditorPanel::pasteInsertCopiedCells(const PatternPosition& cursorPos) { bool result = [&] { const auto decoded = decodeTextForPatternPasting(QApplication::clipboard()->text()); if (!decoded.has_value()) return false; auto [sCol, cells] = decoded.value(); PatternPosition pos = getPasteLeftAbovePosition(sCol, cursorPos, cells.columnSize()); if (config_->getPasteMode() == Configuration::PasteMode::Fill && selLeftAbovePos_.order != -1) { cells = makeCopiedCellsForPasteFull(pos, cells); } if (!bt_->pasteInsertPatternCells( curSongNum_, visTracks_.at(pos.trackVisIdx), pos.colInTrack, pos.order, pos.step, cells)) { return false; } comStack_.lock()->push(new PasteInsertCopiedDataToPatternQtCommand(this)); return true; }(); if (!result) command_result_message_box::showCommandInvokingErrorMessageBox(window()); } PatternPosition PatternEditorPanel::getPasteLeftAbovePosition( int pasteCol, const PatternPosition& cursorPos, size_t cellW) const { PatternPosition pos; Configuration::PasteMode mode = config_->getPasteMode(); if ((mode == Configuration::PasteMode::Selection || mode == Configuration::PasteMode::Fill) && selLeftAbovePos_.colInTrack != -1) { pos = selLeftAbovePos_; pos.colInTrack = pasteCol; } else { pos = cursorPos; if (pasteCol < 3 || (pos.colInTrack - pasteCol) % 2 || (Step::N_COLUMN - pos.colInTrack) < static_cast(cellW)) { pos.colInTrack = pasteCol; } } return pos; } void PatternEditorPanel::cutSelectedCells() { if (selLeftAbovePos_.order == -1) return; copySelectedCells(); eraseSelectedCells(); QString str = QApplication::clipboard()->text(); str.replace("COPY", "CUT"); QApplication::clipboard()->setText(str); } Vector2d PatternEditorPanel::makeCopiedCellsForPasteFull( const PatternPosition& laPos, const Vector2d& cells) { int ow = cells.columnSize(); int oh = cells.rowSize(); int l = laPos.trackVisIdx * Step::N_COLUMN + laPos.colInTrack; int r = selRightBelowPos_.trackVisIdx * Step::N_COLUMN + selRightBelowPos_.colInTrack; int w = r - l + 1; // Real selected region width auto h = static_cast( calculateStepDistance(laPos.order, laPos.step, selRightBelowPos_.order, selRightBelowPos_.step) + 1); int bw = ((ow - 1) / Step::N_COLUMN + 1) * Step::N_COLUMN; std::size_t padSize = bw - ow; int lc = (laPos.colInTrack + ow) % Step::N_COLUMN; std::vector pad(padSize); for (std::size_t i = 0; i < padSize; ++i) { switch (lc) { case 3: case 5: case 7: case 9: pad[i] = "--"; break; default: pad[i] = "-1"; break; } lc = (lc + 1) % Step::N_COLUMN; } Vector2d newCells(h, w); for (std::size_t i = 0; i < h; ++i) { auto rowBeginIt = newCells[i].begin(); for (int dw = w, p = 0; dw > 0; dw -= bw, p += bw) { int ws = std::min(ow, dw); std::copy_n(cells.at(i % oh).begin(), ws, rowBeginIt + p); std::copy_n(pad.begin(), std::min(dw - ws, static_cast(padSize)), rowBeginIt + p + ws); } } return newCells; } void PatternEditorPanel::transposeNote(const PatternPosition& startPos, const PatternPosition& endPos, int semitone) { int beginTrackIdx = (startPos.colInTrack == 0) ? startPos.trackVisIdx : startPos.trackVisIdx + 1; if (beginTrackIdx <= endPos.trackVisIdx) { bt_->transposeNoteInPattern(curSongNum_, visTracks_.at(beginTrackIdx), startPos.order, startPos.step, visTracks_.at(endPos.trackVisIdx), endPos.step, semitone); comStack_.lock()->push(new TransposeNoteInPatternQtCommand(this)); } } void PatternEditorPanel::changeValuesInPattern(const PatternPosition& startPos, const PatternPosition& endPos, int value) { if (startPos.compareCols(endPos) <= 0) { bt_->changeValuesInPattern(curSongNum_, visTracks_.at(startPos.trackVisIdx), startPos.colInTrack, startPos.order, startPos.step, visTracks_.at(endPos.trackVisIdx), endPos.colInTrack, endPos.step, value); comStack_.lock()->push(new ChangeValuesInPatternQtCommand(this)); } } void PatternEditorPanel::toggleTrack(int trackIdx) { int trackNum = visTracks_.at(trackIdx); bt_->setTrackMuteState(trackNum, !bt_->isMute(trackNum)); isMuteElse_ = false; redrawByMaskChanged(); } void PatternEditorPanel::soloTrack(int trackIdx) { int soloTrackNum = visTracks_.at(trackIdx); int trackCnt = static_cast(songStyle_.trackAttribs.size()); if (isMuteElse_) { if (bt_->isMute(soloTrackNum)) { for (int t = 0; t < trackCnt; ++t) bt_->setTrackMuteState(t, t != soloTrackNum); } else { isMuteElse_ = false; for (int t = 0; t < trackCnt; ++t) bt_->setTrackMuteState(t, false); } } else { isMuteElse_ = true; for (int t = 0; t < trackCnt; ++t) bt_->setTrackMuteState(t, (t == soloTrackNum) ? false : isMuteElse_); } redrawByMaskChanged(); } void PatternEditorPanel::setSelectedRectangle(const PatternPosition& start, const PatternPosition& end) { int patMax = static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, end.order)) - 1; if (start.compareCols(end) > 0) { if (start.step > end.step) { selLeftAbovePos_ = end; selRightBelowPos_ = { start.trackVisIdx, start.colInTrack, end.order, (start.step > patMax) ? patMax : start.step }; } else { selLeftAbovePos_ = { end.trackVisIdx, end.colInTrack, end.order, start.step }; selRightBelowPos_ = { start.trackVisIdx, start.colInTrack, end.order, end.step }; } } else { if (start.step > end.step) { selLeftAbovePos_ = { start.trackVisIdx, start.colInTrack, end.order, end.step }; selRightBelowPos_ = { end.trackVisIdx, end.colInTrack, end.order, (start.step > patMax) ? patMax : start.step }; } else { selLeftAbovePos_ = { start.trackVisIdx, start.colInTrack, end.order, start.step }; selRightBelowPos_ = end; } } emit selected(true); backChanged_ = true; repaint(); } bool PatternEditorPanel::isSelectedCell(int trackVisIdx, int colNum, int orderNum, int stepNum) { PatternPosition pos{ trackVisIdx, colNum, orderNum, stepNum }; return (selLeftAbovePos_.compareCols(pos) <= 0 && selRightBelowPos_.compareCols(pos) >= 0 && selLeftAbovePos_.compareRows(pos) <= 0 && selRightBelowPos_.compareRows(pos) >= 0); } void PatternEditorPanel::showPatternContextMenu(const PatternPosition& pos, const QPoint& point) { QMenu menu; // Leave Before Qt5.7.0 style due to windows xp QAction* undo = menu.addAction(tr("&Undo")); undo->setIcon(QIcon(":/icon/undo")); QObject::connect(undo, &QAction::triggered, this, [&]() { if (!bt_->undo()) { command_result_message_box::showCommandUndoingErrorMessageBox(this->window()); return; } comStack_.lock()->undo(); }); QAction* redo = menu.addAction(tr("&Redo")); redo->setIcon(QIcon(":/icon/redo")); QObject::connect(redo, &QAction::triggered, this, [&]() { if (!bt_->redo()) { command_result_message_box::showCommandRedoingErrorMessageBox(this->window()); return; } comStack_.lock()->redo(); }); menu.addSeparator(); QAction* copy = menu.addAction(tr("&Copy")); copy->setIcon(QIcon(":/icon/copy")); QObject::connect(copy, &QAction::triggered, this, &PatternEditorPanel::copySelectedCells); QAction* cut = menu.addAction(tr("Cu&t")); cut->setIcon(QIcon(":/icon/cut")); QObject::connect(cut, &QAction::triggered, this, &PatternEditorPanel::cutSelectedCells); QAction* paste = menu.addAction(tr("&Paste")); paste->setIcon(QIcon(":/icon/paste")); QObject::connect(paste, &QAction::triggered, this, [&]() { pasteCopiedCells(pos); }); auto pasteSp = new QMenu(tr("Paste Specia&l")); menu.addMenu(pasteSp); QAction* pasteMix = pasteSp->addAction(tr("&Mix")); QObject::connect(pasteMix, &QAction::triggered, this, [&]() { pasteMixCopiedCells(pos); }); QAction* pasteOver = pasteSp->addAction(tr("&Overwrite")); QObject::connect(pasteOver, &QAction::triggered, this, [&]() { pasteOverwriteCopiedCells(pos); }); QAction* pasteIns = pasteSp->addAction(tr("&Insert")); QObject::connect(pasteIns, &QAction::triggered, this, [&]() { pasteInsertCopiedCells(pos); }); QAction* erase = menu.addAction(tr("&Erase")); QObject::connect(erase, &QAction::triggered, this, &PatternEditorPanel::eraseSelectedCells); QAction* select = menu.addAction(tr("Select &All")); QObject::connect(select, &QAction::triggered, this, [&]() { onSelectPressed(1); }); menu.addSeparator(); auto pattern = new QMenu(tr("Patter&n")); menu.addMenu(pattern); QAction* interpolate = pattern->addAction(tr("&Interpolate")); QObject::connect(interpolate, &QAction::triggered, this, &PatternEditorPanel::onInterpolatePressed); QAction* reverse = pattern->addAction(tr("&Reverse")); QObject::connect(reverse, &QAction::triggered, this, &PatternEditorPanel::onReversePressed); QAction* replace = pattern->addAction(tr("R&eplace Instrument")); QObject::connect(replace, &QAction::triggered, this, &PatternEditorPanel::onReplaceInstrumentPressed); pattern->addSeparator(); QAction* expand = pattern->addAction(tr("E&xpand")); QObject::connect(expand, &QAction::triggered, this, &PatternEditorPanel::onExpandPressed); QAction* shrink = pattern->addAction(tr("S&hrink")); QObject::connect(shrink, &QAction::triggered, this, &PatternEditorPanel::onShrinkPressed); pattern->addSeparator(); auto transpose = new QMenu(tr("&Transpose")); pattern->addMenu(transpose); QAction* deNote = transpose->addAction(tr("&Decrease Note")); QObject::connect(deNote, &QAction::triggered, this, [&]() { onNoteTransposePressed(-1); }); QAction* inNote = transpose->addAction(tr("&Increase Note")); QObject::connect(inNote, &QAction::triggered, this, [&]() { onNoteTransposePressed(1); }); QAction* deOct = transpose->addAction(tr("D&ecrease Octave")); QObject::connect(deOct, &QAction::triggered, this, [&]() { onNoteTransposePressed(-12); }); QAction* inOct = transpose->addAction(tr("I&ncrease Octave")); QObject::connect(inOct, &QAction::triggered, this, [&]() { onNoteTransposePressed(12); }); auto changeVals = new QMenu(tr("&Change Values")); pattern->addMenu(changeVals); QAction* fdeVal = changeVals->addAction(tr("Fine &Decrease Values")); QObject::connect(fdeVal, &QAction::triggered, this, [&]() { onChangeValuesPressed(-1); }); QAction* finVal = changeVals->addAction(tr("Fine &Increase Values")); QObject::connect(finVal, &QAction::triggered, this, [&]() { onChangeValuesPressed(1); }); QAction* cdeVal = changeVals->addAction(tr("Coarse D&ecrease Values")); QObject::connect(cdeVal, &QAction::triggered, this, [&]() { onChangeValuesPressed(-16); }); QAction* cinVal = changeVals->addAction(tr("Coarse I&ncrease Values")); QObject::connect(cinVal, &QAction::triggered, this, [&]() { onChangeValuesPressed(16); }); menu.addSeparator(); QAction* toggle = menu.addAction(tr("To&ggle Track")); QObject::connect(toggle, &QAction::triggered, this, [&] { toggleTrack(pos.trackVisIdx); }); QAction* solo = menu.addAction(tr("&Solo Track")); QObject::connect(solo, &QAction::triggered, this, [&] { soloTrack(pos.trackVisIdx); }); menu.addSeparator(); QAction* exeff = menu.addAction(tr("Expand E&ffect Column")); QObject::connect(exeff, &QAction::triggered, this, [&] { onExpandEffectColumnPressed(pos.trackVisIdx); }); QAction* sheff = menu.addAction(tr("Shrin&k Effect Column")); QObject::connect(sheff, &QAction::triggered, this, [&] { onShrinkEffectColumnPressed(pos.trackVisIdx); }); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) undo->setShortcutVisibleInContextMenu(true); redo->setShortcutVisibleInContextMenu(true); copy->setShortcutVisibleInContextMenu(true); cut->setShortcutVisibleInContextMenu(true); paste->setShortcutVisibleInContextMenu(true); pasteMix->setShortcutVisibleInContextMenu(true); erase->setShortcutVisibleInContextMenu(true); select->setShortcutVisibleInContextMenu(true); interpolate->setShortcutVisibleInContextMenu(true); reverse->setShortcutVisibleInContextMenu(true); replace->setShortcutVisibleInContextMenu(true); deNote->setShortcutVisibleInContextMenu(true); inNote->setShortcutVisibleInContextMenu(true); deOct->setShortcutVisibleInContextMenu(true); inOct->setShortcutVisibleInContextMenu(true); fdeVal->setShortcutVisibleInContextMenu(true); finVal->setShortcutVisibleInContextMenu(true); cdeVal->setShortcutVisibleInContextMenu(true); cinVal->setShortcutVisibleInContextMenu(true); toggle->setShortcutVisibleInContextMenu(true); solo->setShortcutVisibleInContextMenu(true); exeff->setShortcutVisibleInContextMenu(true); sheff->setShortcutVisibleInContextMenu(true); #endif auto shortcuts = config_->getShortcuts(); undo->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Z)); redo->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Y)); undo->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Z)); copy->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_C)); cut->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_X)); paste->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_V)); pasteMix->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::PasteMix))); pasteOver->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::PasteOverwrite))); pasteIns->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::PasteInsert))); erase->setShortcut(QKeySequence(Qt::Key_Delete)); select->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::SelectAll))); interpolate->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::Interpolate))); reverse->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::Reverse))); replace->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::ReplaceInstrument))); expand->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::ExpandPattern))); shrink->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::ShrinkPattern))); deNote->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::DecreaseNote))); inNote->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::IncreaseNote))); deOct->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::DecreaseOctave))); inOct->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::IncreaseOctave))); fdeVal->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::FineDecreaseValues))); finVal->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::FineIncreaseValues))); cdeVal->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::CoarseDecreaseValues))); cinVal->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::CoarseIncreaseValuse))); toggle->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::ToggleTrack))); solo->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::SoloTrack))); exeff->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::ExpandEffect))); sheff->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::ShrinkEffect))); if (bt_->isJamMode() || pos.order < 0 || pos.trackVisIdx < 0) { copy->setEnabled(false); cut->setEnabled(false); paste->setEnabled(false); pasteMix->setEnabled(false); pasteOver->setEnabled(false); pasteIns->setEnabled(false); erase->setEnabled(false); interpolate->setEnabled(false); reverse->setEnabled(false); replace->setEnabled(false); expand->setEnabled(false); shrink->setEnabled(false); deNote->setEnabled(false); inNote->setEnabled(false); deOct->setEnabled(false); inOct->setEnabled(false); fdeVal->setEnabled(false); finVal->setEnabled(false); cdeVal->setEnabled(false); cinVal->setEnabled(false); } else { QString clipText = QApplication::clipboard()->text(); if (!clipText.startsWith("PATTERN_COPY") && !clipText.startsWith("PATTERN_CUT")) { paste->setEnabled(false); pasteMix->setEnabled(false); pasteOver->setEnabled(false); pasteIns->setEnabled(false); } if (selRightBelowPos_.order < 0 || !isSelectedCell(pos.trackVisIdx, pos.colInTrack, pos.order, pos.step)) { copy->setEnabled(false); cut->setEnabled(false); erase->setEnabled(false); interpolate->setEnabled(false); reverse->setEnabled(false); replace->setEnabled(false); expand->setEnabled(false); shrink->setEnabled(false); deNote->setEnabled(false); inNote->setEnabled(false); deOct->setEnabled(false); inOct->setEnabled(false); fdeVal->setEnabled(false); finVal->setEnabled(false); cdeVal->setEnabled(false); cinVal->setEnabled(false); } } if (!comStack_.lock()->canUndo()) { undo->setEnabled(false); } if (!comStack_.lock()->canRedo()) { redo->setEnabled(false); } if (pos.trackVisIdx < 0) { toggle->setEnabled(false); solo->setEnabled(false); exeff->setEnabled(false); sheff->setEnabled(false); } menu.exec(mapToGlobal(point)); } /********** Slots **********/ void PatternEditorPanel::onHScrollBarChanged(int num) { Ui::EventGuard eg(isIgnoreToSlider_); // Skip if position has already changed in panel if (config_->getMoveCursorByHorizontalScroll()) { if (int dif = num - calculateColNumInRow(curPos_.trackVisIdx, curPos_.colInTrack)) moveCursorToRight(dif); } else { if (int dif = num - leftTrackVisIdx_) moveViewToRight(dif); } } void PatternEditorPanel::onVScrollBarChanged(int num) { Ui::EventGuard eg(isIgnoreToSlider_); // Skip if position has already changed in panel if (int dif = num - curPos_.step) moveCursorToDown(dif); } void PatternEditorPanel::onOrderListCurrentTrackChanged(int idx) { Ui::EventGuard eg(isIgnoreToOrder_); int dif = calculateColumnDistance(curPos_.trackVisIdx, curPos_.colInTrack, idx, 0); moveCursorToRight(dif); } void PatternEditorPanel::onOrderListCurrentOrderChanged(int num) { Ui::EventGuard eg(isIgnoreToOrder_); int step = std::min(curPos_.step, static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, num)) - 1); int dif = calculateStepDistance(curPos_.order, curPos_.step, num, step); moveCursorToDown(dif); } void PatternEditorPanel::onOrderListEdited() { // Reset position memory hovPos_ = { -1, -1, -1, -1 }; mousePressPos_ = { -1, -1, -1, -1 }; mouseReleasePos_ = { -1, -1, -1, -1 }; selLeftAbovePos_ = { -1, -1, -1, -1 }; selRightBelowPos_ = { -1, -1, -1, -1 }; shiftPressedPos_ = { -1, -1, -1, -1 }; selectAllState_ = -1; emit selected(false); redrawByPatternChanged(true); } void PatternEditorPanel::onDefaultPatternSizeChanged() { // Check pattern size int end = static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order)); if (curPos_.step >= end) curPos_.step = end - 1; redrawByPatternChanged(true); } void PatternEditorPanel::onShortcutUpdated() { auto shortcuts = config_->getShortcuts(); hlUpSc_.setKey(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::PrevHighlighted))); hlUpWSSc_.setKey(gui_utils::strToKeySeq("Shift+" + shortcuts.at(Configuration::ShortcutAction::PrevHighlighted))); hlDnSc_.setKey(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::NextHighlighted))); hlDnWSSc_.setKey(gui_utils::strToKeySeq("Shift+" + shortcuts.at(Configuration::ShortcutAction::NextHighlighted))); keyOffSc_.setKey(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::KeyOff))); keyCutSc_.setKey(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::KeyCut))); echoBufSc_.setKey(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::EchoBuffer))); expandColSc_.setKey(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::ExpandEffect))); shrinkColSc_.setKey(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::ShrinkEffect))); } void PatternEditorPanel::setPatternHighlight1Count(int count) { hl1Cnt_ = count; backChanged_ = true; textChanged_ = true; repaint(); } void PatternEditorPanel::setPatternHighlight2Count(int count) { hl2Cnt_ = count; backChanged_ = true; textChanged_ = true; repaint(); } void PatternEditorPanel::setEditableStep(int n) { editableStepCnt_ = n; } void PatternEditorPanel::onSongLoaded() { // NOTE: Temporary fix for https://github.com/BambooTracker/BambooTracker/issues/276 isInitedFirstMod_.store(true); // Initialize cursor position curSongNum_ = bt_->getCurrentSongNumber(); SongType prevType = songStyle_.type; songStyle_ = bt_->getSongStyle(curSongNum_); visTracks_ = gui_utils::adaptVisibleTrackList(visTracks_, prevType, songStyle_.type); rightEffn_.resize(visTracks_.size()); std::transform(visTracks_.begin(), visTracks_.end(), rightEffn_.begin(), [&](int t) { return static_cast(bt_->getEffectDisplayWidth(curSongNum_, t)); }); curPos_ = { visTracks_.front(), 0, bt_->getCurrentOrderNumber(), bt_->getCurrentStepNumber() }; // Set cursor to the most lest-placed visible track if (visTracks_.front() != bt_->getCurrentTrackAttribute().number) { bt_->setCurrentTrack(visTracks_.front()); } leftTrackVisIdx_ = 0; updateTracksWidthFromLeftToEnd(); hovPos_ = { -1, -1, -1, -1 }; mousePressPos_ = { -1, -1, -1, -1 }; mouseReleasePos_ = { -1, -1, -1, -1 }; selLeftAbovePos_ = { -1, -1, -1, -1 }; selRightBelowPos_ = { -1, -1, -1, -1 }; shiftPressedPos_ = { -1, -1, -1, -1 }; markerPos_ = { -1, -1, -1, -1 }; entryCnt_ = 0; selectAllState_ = -1; emit selected(false); redrawAll(); } void PatternEditorPanel::onDeletePressed() { if (bt_->isJamMode()) return; if (selLeftAbovePos_.order != -1) { // Delete region eraseSelectedCells(); } else { switch (curPos_.colInTrack) { case 0: bt_->eraseStepNote(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.order, curPos_.step); comStack_.lock()->push(new EraseStepQtCommand(this)); break; case 1: bt_->eraseStepInstrument(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.order, curPos_.step); comStack_.lock()->push(new EraseInstrumentInStepQtCommand(this)); break; case 2: bt_->eraseStepVolume(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.order, curPos_.step); comStack_.lock()->push(new EraseVolumeInStepQtCommand(this)); break; case 3: case 5: case 7: case 9: bt_->eraseStepEffect(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.order, curPos_.step, (curPos_.colInTrack - 3) / 2); comStack_.lock()->push(new EraseEffectInStepQtCommand(this)); break; case 4: case 6: case 8: case 10: bt_->eraseStepEffectValue(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.order, curPos_.step, (curPos_.colInTrack - 4) / 2); comStack_.lock()->push(new EraseEffectValueInStepQtCommand(this)); break; } if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(editableStepCnt_); } } void PatternEditorPanel::onPastePressed() { if (!bt_->isJamMode()) pasteCopiedCells(curPos_); } void PatternEditorPanel::onPasteMixPressed() { if (!bt_->isJamMode()) pasteMixCopiedCells(curPos_); } void PatternEditorPanel::onPasteOverwritePressed() { if (!bt_->isJamMode()) pasteOverwriteCopiedCells(curPos_); } void PatternEditorPanel::onPasteInsertPressed() { if (!bt_->isJamMode()) pasteInsertCopiedCells(curPos_); } void PatternEditorPanel::onSelectPressed(int type) { switch (type) { case 0: // None { selLeftAbovePos_ = { -1, -1, -1, -1 }; selRightBelowPos_ = { -1, -1, -1, -1 }; selectAllState_ = -1; emit selected(false); backChanged_ = true; repaint(); break; } case 1: // All { selectAllState_ = (selectAllState_ + 1) % 2; int max = static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order)) - 1; PatternPosition start, end; if (selectAllState_) { start = { 0, 0, curPos_.order, 0 }; end = { static_cast(visTracks_.size() - 1), 10, curPos_.order, max }; } else { start = { curPos_.trackVisIdx, 0, curPos_.order, 0 }; end = { curPos_.trackVisIdx, 10, curPos_.order, max }; } setSelectedRectangle(start, end); break; } case 2: // Row { selectAllState_ = -1; PatternPosition start = { 0, 0, curPos_.order, curPos_.step }; PatternPosition end = { static_cast(visTracks_.size() - 1), 10, curPos_.order, curPos_.step }; setSelectedRectangle(start, end); break; } case 3: // Column { selectAllState_ = -1; PatternPosition start = { curPos_.trackVisIdx, curPos_.colInTrack, curPos_.order, 0 }; PatternPosition end = { curPos_.trackVisIdx, curPos_.colInTrack, curPos_.order, static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order) - 1) }; setSelectedRectangle(start, end); break; } case 4: // Pattern { selectAllState_ = -1; PatternPosition start = { curPos_.trackVisIdx, 0, curPos_.order, 0 }; PatternPosition end = { curPos_.trackVisIdx, 10, curPos_.order, static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order) - 1) }; setSelectedRectangle(start, end); break; } case 5: // Order { selectAllState_ = -1; PatternPosition start = { 0, 0, curPos_.order, 0 }; PatternPosition end = { static_cast(visTracks_.size() - 1), 10, curPos_.order, static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order) - 1) }; setSelectedRectangle(start, end); break; } } } void PatternEditorPanel::onNoteTransposePressed(int semitone) { if (bt_->isJamMode()) return; if (selLeftAbovePos_.order != -1) transposeNote(selLeftAbovePos_, selRightBelowPos_, semitone); else transposeNote(curPos_, curPos_, semitone); } void PatternEditorPanel::onToggleTrackPressed() { toggleTrack(curPos_.trackVisIdx); } void PatternEditorPanel::onSoloTrackPressed() { soloTrack(curPos_.trackVisIdx); } void PatternEditorPanel::onUnmuteAllPressed() { int trackCnt = static_cast(songStyle_.trackAttribs.size()); for (int t = 0; t < trackCnt; ++t) bt_->setTrackMuteState(t, false); isMuteElse_ = false; redrawByMaskChanged(); } void PatternEditorPanel::onExpandPressed() { if (selLeftAbovePos_.order == -1) return; bt_->expandPattern(curSongNum_, visTracks_.at(selLeftAbovePos_.trackVisIdx), selLeftAbovePos_.colInTrack, selLeftAbovePos_.order, selLeftAbovePos_.step, visTracks_.at(selRightBelowPos_.trackVisIdx), selRightBelowPos_.colInTrack, selRightBelowPos_.step); comStack_.lock()->push(new ExpandPatternQtCommand(this)); } void PatternEditorPanel::onShrinkPressed() { if (selLeftAbovePos_.order == -1) return; bt_->shrinkPattern(curSongNum_, visTracks_.at(selLeftAbovePos_.trackVisIdx), selLeftAbovePos_.colInTrack, selLeftAbovePos_.order, selLeftAbovePos_.step, visTracks_.at(selRightBelowPos_.trackVisIdx), selRightBelowPos_.colInTrack, selRightBelowPos_.step); comStack_.lock()->push(new ShrinkPatternQtCommand(this)); } void PatternEditorPanel::onInterpolatePressed() { if (selLeftAbovePos_.order == -1) return; bt_->interpolatePattern(curSongNum_, visTracks_.at(selLeftAbovePos_.trackVisIdx), selLeftAbovePos_.colInTrack, selLeftAbovePos_.order, selLeftAbovePos_.step, visTracks_.at(selRightBelowPos_.trackVisIdx), selRightBelowPos_.colInTrack, selRightBelowPos_.step); comStack_.lock()->push(new InterpolatePatternQtCommand(this)); } void PatternEditorPanel::onReversePressed() { if (selLeftAbovePos_.order == -1) return; bt_->reversePattern(curSongNum_, visTracks_.at(selLeftAbovePos_.trackVisIdx), selLeftAbovePos_.colInTrack, selLeftAbovePos_.order, selLeftAbovePos_.step, visTracks_.at(selRightBelowPos_.trackVisIdx), selRightBelowPos_.colInTrack, selRightBelowPos_.step); comStack_.lock()->push(new ReversePatternQtCommand(this)); } void PatternEditorPanel::onReplaceInstrumentPressed() { if (selLeftAbovePos_.order == -1) return; int curInst = bt_->getCurrentInstrumentNumber(); if (curInst == -1) return; int beginTrackIdx = (selLeftAbovePos_.colInTrack < 2) ? selLeftAbovePos_.trackVisIdx : (selLeftAbovePos_.trackVisIdx + 1); int endTrackIdx = (selRightBelowPos_.colInTrack == 0) ? (selRightBelowPos_.trackVisIdx - 1) : selRightBelowPos_.trackVisIdx; if (beginTrackIdx <= endTrackIdx) { bt_->replaceInstrumentInPattern(curSongNum_, visTracks_.at(beginTrackIdx), selLeftAbovePos_.order, selLeftAbovePos_.step, visTracks_.at(endTrackIdx), selRightBelowPos_.step, curInst); comStack_.lock()->push(new ReplaceInstrumentInPatternQtCommand(this)); } } void PatternEditorPanel::onExpandEffectColumnPressed(int trackVisIdx) { size_t ti = static_cast(trackVisIdx); if (rightEffn_.at(ti) == 3) return; bt_->setEffectDisplayWidth(curSongNum_, visTracks_.at(trackVisIdx), static_cast(++rightEffn_[ti])); updateTracksWidthFromLeftToEnd(); if (config_->getMoveCursorByHorizontalScroll()) { emit effectColsCompanded(calculateColNumInRow(curPos_.trackVisIdx, curPos_.colInTrack), getFullColmunSize()); } else { emit effectColsCompanded(leftTrackVisIdx_, getScrollableCountByTrack()); } redrawAll(); } void PatternEditorPanel::onShrinkEffectColumnPressed(int trackVisIdx) { size_t ti = static_cast(trackVisIdx); if (rightEffn_.at(ti) == 0) return; bt_->setEffectDisplayWidth(curSongNum_, visTracks_.at(trackVisIdx), static_cast(--rightEffn_[ti])); updateTracksWidthFromLeftToEnd(); if (config_->getMoveCursorByHorizontalScroll()) { emit effectColsCompanded(calculateColNumInRow(curPos_.trackVisIdx, curPos_.colInTrack), getFullColmunSize()); } else { emit effectColsCompanded(leftTrackVisIdx_, getScrollableCountByTrack()); } redrawAll(); } void PatternEditorPanel::onFollowModeChanged() { curPos_.setRows(bt_->getCurrentOrderNumber(), bt_->getCurrentStepNumber()); emit vScrollBarChangeRequested( curPos_.step, static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order)) - 1); // Force redraw all area followModeChanged_ = true; redrawPatterns(); } void PatternEditorPanel::onChangeValuesPressed(int value) { if (bt_->isJamMode()) return; if (selLeftAbovePos_.order != -1) changeValuesInPattern(selLeftAbovePos_, selRightBelowPos_, value); else changeValuesInPattern(curPos_, curPos_, value); } void PatternEditorPanel::onPlayStepPressed() { moveCursorToDown(1); } /********** Events **********/ bool PatternEditorPanel::event(QEvent *event) { switch (event->type()) { case QEvent::KeyPress: return keyPressed(dynamic_cast(event)); case QEvent::KeyRelease: return keyReleased(dynamic_cast(event)); case QEvent::HoverMove: return mouseHoverd(dynamic_cast(event)); default: return QWidget::event(event); } } bool PatternEditorPanel::keyPressed(QKeyEvent *event) { /* General Keys */ switch (event->key()) { case Qt::Key_Shift: shiftPressedPos_ = curPos_; return true; case Qt::Key_Tab: if (curPos_.trackVisIdx == static_cast(visTracks_.size()) - 1) { if (config_->getWarpCursor()) moveCursorToRight(-calculateColNumInRow(curPos_.trackVisIdx, curPos_.colInTrack)); } else { moveCursorToRight(5 + 2 * rightEffn_[static_cast(curPos_.trackVisIdx)] - curPos_.colInTrack); } return true; case Qt::Key_Backtab: if (curPos_.trackVisIdx == 0) { if (config_->getWarpCursor()) moveCursorToRight(getFullColmunSize() - 1); } else { moveCursorToRight(-5 - 2 * rightEffn_[static_cast(curPos_.trackVisIdx) - 1] - curPos_.colInTrack); } return true; case Qt::Key_Insert: if (bt_->isJamMode()) { return false; } else { insertStep(); return true; } case Qt::Key_Backspace: if (bt_->isJamMode()) { return false; } else { deletePreviousStep(); return true; } case Qt::Key_Menu: { QPoint point = calculateCurrentCursorPosition(); point.setX(point.x() + 24); point.setY(point.y() - 16); showPatternContextMenu(curPos_, point); return true; } default: if (!bt_->isJamMode()) { // Pattern edit if (!config_->getKeyRepetition() && event->isAutoRepeat()) return false; switch (curPos_.colInTrack) { case 0: return enterToneData(event); case 1: { auto modifiers = event->modifiers(); if (modifiers.testFlag(Qt::NoModifier) || modifiers.testFlag(Qt::KeypadModifier)) return enterInstrumentData(event->key()); break; } case 2: { auto modifiers = event->modifiers(); if (modifiers.testFlag(Qt::NoModifier) || modifiers.testFlag(Qt::KeypadModifier)) return enterVolumeData(event->key()); break; } case 3: case 5: case 7: case 9: { auto modifiers = event->modifiers(); if (modifiers.testFlag(Qt::NoModifier) || modifiers.testFlag(Qt::KeypadModifier)) return enterEffectID(event->key()); break; } case 4: case 6: case 8: case 10: { auto modifiers = event->modifiers(); if (modifiers.testFlag(Qt::NoModifier) || modifiers.testFlag(Qt::KeypadModifier)) return enterEffectValue(event->key()); break; } } } return false; } } bool PatternEditorPanel::keyReleased(QKeyEvent* event) { switch (event->key()) { case Qt::Key_Shift: shiftPressedPos_ = { -1, -1, -1, -1 }; return true; default: return false; } } void PatternEditorPanel::paintEvent(QPaintEvent *event) { if (bt_ && isInitedFirstMod_.load()) { // Check the cursor position and clamp it if neccesary. int odrSize = static_cast(bt_->getOrderSize(curSongNum_)); if (curPos_.order >= odrSize) { curPos_.setRows(odrSize - 1, 0); } else { int patternSize = bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order); if (curPos_.step >= patternSize) { curPos_.step = patternSize - 1; } } const QRect& area = event->rect(); if (area.x() == 0 && area.y() == 0) { drawPattern(area); } else { drawPattern(rect()); } } } void PatternEditorPanel::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); funcResize(); redrawAll(); } void PatternEditorPanel::mousePressEvent(QMouseEvent* event) { mousePressPos_ = hovPos_; doubleClickPos_ = mousePressPos_; mouseReleasePos_ = { -1, -1, -1, -1 }; isPressedPlus_ = false; isPressedMinus_ = false; if (event->button() == Qt::LeftButton) { if (mousePressPos_.order == -2 && mousePressPos_.trackVisIdx >= 0) { int w = calculateTracksWidthWithRowNum(leftTrackVisIdx_, mousePressPos_.trackVisIdx - 1) + hdMuteToggleWidth_ + stepFontWidth_ / 2; if (w < event->pos().x() && event->pos().x() < w + hdEffCompandButtonWidth_ + stepFontWidth_) { if (event->pos().y() < headerHeight_ / 2) isPressedPlus_ = true; else isPressedMinus_ = true; } } selLeftAbovePos_ = { -1, -1, -1, -1 }; selRightBelowPos_ = { -1, -1, -1, -1 }; selectAllState_ = -1; emit selected(false); } } void PatternEditorPanel::mouseMoveEvent(QMouseEvent* event) { if (event->buttons() & Qt::LeftButton) { if (mousePressPos_.trackVisIdx < 0 || mousePressPos_.order < 0) return; // Start point is out of range if (hovPos_.trackVisIdx >= 0 && hovPos_.order >= 0) { setSelectedRectangle(mousePressPos_, hovPos_); } #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QPointF pos = event->position(); #else QPoint pos = event->pos(); #endif if (pos.x() < stepNumWidth_ && leftTrackVisIdx_ > 0) { if (config_->getMoveCursorByHorizontalScroll()) { moveCursorToRight(-(5 + 2 * rightEffn_.at(static_cast(leftTrackVisIdx_) - 1))); } else { moveViewToRight(-1); } } else if (pos.x() > geometry().width() - stepNumWidth_ && hovPos_.trackVisIdx != -1) { if (config_->getMoveCursorByHorizontalScroll()) { moveCursorToRight(5 + 2 * rightEffn_.at(static_cast(leftTrackVisIdx_))); } else { moveViewToRight(1); } } if (pos.y() < headerHeight_ + stepFontHeight_) { if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(-1); } else if (pos.y() > geometry().height() - stepFontHeight_) { if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(1); } } } void PatternEditorPanel::mouseReleaseEvent(QMouseEvent* event) { mouseReleasePos_ = hovPos_; switch (event->button()) { case Qt::LeftButton: if (mousePressPos_ == mouseReleasePos_) { // Jump cell if (hovPos_.order >= 0 && hovPos_.step >= 0 && hovPos_.trackVisIdx >= 0 && hovPos_.colInTrack >= 0) { int horDif = calculateColumnDistance(curPos_.trackVisIdx, curPos_.colInTrack, hovPos_.trackVisIdx, hovPos_.colInTrack); int verDif = calculateStepDistance(curPos_.order, curPos_.step, hovPos_.order, hovPos_.step); moveCursorToRight(horDif); if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(verDif); } else if (hovPos_.order == -2 && hovPos_.trackVisIdx >= 0) { // Header if (isPressedPlus_) { onExpandEffectColumnPressed(hovPos_.trackVisIdx); } else if (isPressedMinus_) { onShrinkEffectColumnPressed(hovPos_.trackVisIdx); } else { toggleTrack(hovPos_.trackVisIdx); int horDif = calculateColumnDistance(curPos_.trackVisIdx, curPos_.colInTrack, hovPos_.trackVisIdx, 0); moveCursorToRight(horDif); } } else if (hovPos_.trackVisIdx == -2 && hovPos_.order >= 0 && hovPos_.step >= 0) { // Step number if (!bt_->isPlaySong() || !bt_->isFollowPlay()) { int verDif = calculateStepDistance(curPos_.order, curPos_.step, hovPos_.order, hovPos_.step); moveCursorToDown(verDif); } } } break; case Qt::RightButton: // Show context menu { if (mousePressPos_.order == -2) { // Header QMenu menu; // Leave Before Qt5.7.0 style due to windows xp QAction* toggle = menu.addAction(tr("To&ggle Track")); QObject::connect(toggle, &QAction::triggered, this, [&] { toggleTrack(mousePressPos_.trackVisIdx); }); QAction* solo = menu.addAction(tr("&Solo Track")); QObject::connect(solo, &QAction::triggered, this, [&] { soloTrack(mousePressPos_.trackVisIdx); }); QAction* unmute = menu.addAction(tr("&Unmute All Tracks")); QObject::connect(unmute, &QAction::triggered, this, &PatternEditorPanel::onUnmuteAllPressed); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) toggle->setShortcutVisibleInContextMenu(true); solo->setShortcutVisibleInContextMenu(true); #endif auto shortcuts = config_->getShortcuts(); toggle->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::ToggleTrack))); solo->setShortcut(gui_utils::strToKeySeq(shortcuts.at(Configuration::ShortcutAction::SoloTrack))); if (mousePressPos_.trackVisIdx < 0) { toggle->setEnabled(false); solo->setEnabled(false); unmute->setEnabled(false); } menu.exec(mapToGlobal(event->pos())); } else { // Pattern showPatternContextMenu(mousePressPos_, event->pos()); } break; } case Qt::XButton1: { if (!bt_->isPlaySong() || !bt_->isFollowPlay()) { int order = curPos_.order - 1; if (order < 0) order = static_cast(bt_->getOrderSize(curSongNum_)) - 1; int step = std::min( curPos_.step, static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, order)) - 1); int d = calculateStepDistance(curPos_.order, curPos_.step, order, step); moveCursorToDown(d); } break; } case Qt::XButton2: { if (!bt_->isPlaySong() || !bt_->isFollowPlay()) { int order = curPos_.order + 1; if (static_cast(bt_->getOrderSize(curSongNum_)) - 1 < order) order = 0; int step = std::min( curPos_.step, static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, order)) - 1); int d = calculateStepDistance(curPos_.order, curPos_.step, order, step); moveCursorToDown(d); } break; } default: break; } mousePressPos_ = { -1, -1, -1, -1 }; mouseReleasePos_ = { -1, -1, -1, -1 }; } void PatternEditorPanel::mouseDoubleClickEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton) { if (doubleClickPos_.order >= 0) { if (!config_->getDontSelectOnDoubleClick()) { if (doubleClickPos_.trackVisIdx >= 0) { onSelectPressed(4); return; } else if (doubleClickPos_.trackVisIdx == -2) { onSelectPressed(5); return; } } } else if (doubleClickPos_.order == -2) { if (doubleClickPos_.trackVisIdx >= 0 && !isPressedPlus_ && !isPressedMinus_) { bool flag = true; int trackCnt = static_cast(songStyle_.trackAttribs.size()); int clickedNum = visTracks_.at(doubleClickPos_.trackVisIdx); for (int t = 0; t < trackCnt; ++t) { if (t != clickedNum) flag &= bt_->isMute(t); } if (flag) onUnmuteAllPressed(); else soloTrack(doubleClickPos_.trackVisIdx); return; } } } // Else mousePressEvent(event); } bool PatternEditorPanel::mouseHoverd(QHoverEvent *event) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QPointF pos = event->position(); #else QPoint pos = event->pos(); #endif PatternPosition oldPos = hovPos_; // Detect Step if (pos.y() <= headerHeight_) { // Track header hovPos_.setRows(-2, -2); } else { if (pos.y() < curRowY_) { int tmpOdr = curPos_.order; int tmpStep = curPos_.step + static_cast(std::ceil((pos.y() - curRowY_) / stepFontHeight_)) - 1; while (true) { if (tmpStep < 0) { if (tmpOdr == 0) { hovPos_.setRows(-1, -1); break; } else { tmpStep += bt_->getPatternSizeFromOrderNumber(curSongNum_, --tmpOdr); } } else { hovPos_.setRows(tmpOdr, tmpStep); break; } } } else { int tmpOdr = curPos_.order; int tmpStep = curPos_.step + static_cast(std::floor((pos.y() - curRowY_) / stepFontHeight_)); while (true) { int endStep = static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, tmpOdr)); if (tmpStep < endStep) { hovPos_.setRows(tmpOdr, tmpStep); break; } else { if (tmpOdr == static_cast(bt_->getOrderSize(curSongNum_)) - 1) { hovPos_.setRows(-1, -1); break; } else { ++tmpOdr; tmpStep -= endStep; } } } } } // Detect column if (pos.x() <= stepNumWidth_) { // Row number hovPos_.setCols(-2, -2); } else { int tmpWidth = stepNumWidth_; for (int i = leftTrackVisIdx_; ; ) { tmpWidth += (toneNameWidth_ + widthSpaceDbl_); if (pos.x() <= tmpWidth) { hovPos_.setCols(i, 0); break; } tmpWidth += (instWidth_ + widthSpaceDbl_); if (pos.x() <= tmpWidth) { hovPos_.setCols(i, 1); break; } tmpWidth += (volWidth_ + widthSpaceDbl_); if (pos.x() <= tmpWidth) { hovPos_.setCols(i, 2); break; } bool flag = false; for (int j = 0; j <= rightEffn_.at(static_cast(i)); ++j) { tmpWidth += (effIDWidth_ + widthSpace_); if (pos.x() <= tmpWidth) { hovPos_.setCols(i, 3 + 2 * j); flag = true; break; } tmpWidth += (effValWidth_ + widthSpace_); if (pos.x() <= tmpWidth) { hovPos_.setCols(i, 4 + 2 * j); flag = true; break; } } if (flag) break; ++i; if (i == static_cast(visTracks_.size())) { hovPos_.setCols(-1, -1); break; } } } if (hovPos_ != oldPos) redrawByHoverChanged(); return true; } void PatternEditorPanel::wheelEvent(QWheelEvent *event) { if (bt_->isPlaySong() && bt_->isFollowPlay()) return; int cnt = event->angleDelta().y() / 120; if (event->modifiers().testFlag(Qt::ControlModifier)) { onNoteTransposePressed(cnt); } else if (event->modifiers().testFlag(Qt::ShiftModifier)) { onChangeValuesPressed(cnt); } else { moveCursorToDown(-cnt); } } void PatternEditorPanel::leaveEvent(QEvent*) { // Clear mouse hover selection hovPos_ = { -1, -1, -1, -1 }; } void PatternEditorPanel::midiThreadReceivedEvent(double delay, const uint8_t *msg, size_t len, void *userData) { PatternEditorPanel *self = reinterpret_cast(userData); Q_UNUSED(delay) // Note-On/Note-Off if (len == 3 && (msg[0] & 0xe0) == 0x80) { uint8_t status = msg[0]; uint8_t key = msg[1]; uint8_t velocity = msg[2]; QMetaMethod method = self->metaObject()->method(self->midiKeyEventMethod_); method.invoke(self, Qt::QueuedConnection, Q_ARG(uchar, status), Q_ARG(uchar, key), Q_ARG(uchar, velocity)); } } void PatternEditorPanel::midiKeyEvent(uchar status, uchar key, uchar velocity) { if (!bt_->isJamMode()) { bool release = ((status & 0xf0) == 0x80) || velocity == 0; if (!release) { setStepKeyOn(Note(static_cast(key) - 12)); } } } BambooTracker-0.6.5/BambooTracker/gui/pattern_editor/pattern_editor_panel.hpp000066400000000000000000000221431476276175200275150ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #ifndef PATTERN_EDITOR_PANEL_HPP #define PATTERN_EDITOR_PANEL_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bamboo_tracker.hpp" #include "configuration.hpp" #include "song.hpp" #include "vector_2d.hpp" #include "gui/pattern_editor/pattern_position.hpp" #include "gui/color_palette.hpp" class PatternEditorPanel : public QWidget { Q_OBJECT public: explicit PatternEditorPanel(QWidget *parent = nullptr); ~PatternEditorPanel() override; void setCore(std::shared_ptr core); bool isReadyCore() const; void setCommandStack(std::weak_ptr stack); void setConfiguration(std::shared_ptr config); void setColorPallete(std::shared_ptr palette); void changeEditable(); int getFullColmunSize() const; void updatePositionByStepUpdate(bool isFirstUpdate, bool forceJump = false, bool trackChanged = false); int getScrollableCountByTrack() const; void changeMarker(); void copySelectedCells(); void cutSelectedCells(); void redrawByPatternChanged(bool patternSizeChanged = false); void redrawByFocusChanged(); void redrawByHoverChanged(); void redrawByMaskChanged(); void redrawPatterns(); void redrawAll(); void resetEntryCount(); void waitPaintFinish(); QFont getHeaderFont() const; QFont getRowsFont() const; QFont getDefaultHeaderFont() const; QFont getDefaultRowsFont() const; void setFonts(const QFont& headerFont, const QFont& rowsFont); void setVisibleTracks(std::vector tracks); std::vector getVisibleTracks() const; public slots: void onHScrollBarChanged(int num); void onVScrollBarChanged(int num); void onOrderListCurrentTrackChanged(int idx); void onOrderListCurrentOrderChanged(int num); void onOrderListEdited(); void onDefaultPatternSizeChanged(); void onShortcutUpdated(); void setPatternHighlight1Count(int count); void setPatternHighlight2Count(int count); void setEditableStep(int n); void onSongLoaded(); void onDeletePressed(); void onPastePressed(); void onPasteMixPressed(); void onPasteOverwritePressed(); void onPasteInsertPressed(); /// 0: None /// 1: All /// 2: Row /// 3: Column /// 4: Pattern /// 5: Order void onSelectPressed(int type); void onNoteTransposePressed(int semitone); void onToggleTrackPressed(); void onSoloTrackPressed(); void onUnmuteAllPressed(); void onExpandPressed(); void onShrinkPressed(); void onInterpolatePressed(); void onReversePressed(); void onReplaceInstrumentPressed(); void onExpandEffectColumnPressed(int trackVisIdx); void onShrinkEffectColumnPressed(int trackVisIdx); void onFollowModeChanged(); void onChangeValuesPressed(int value); void onPlayStepPressed(); signals: void hScrollBarChangeRequested(int num); void vScrollBarChangeRequested(int num, int max); void currentTrackChanged(int num); void currentOrderChanged(int num, int max); void effectColsCompanded(int num, int max); void selected(bool isSelected); void instrumentEntered(int num); void volumeEntered(int volume); void effectEntered(QString text); protected: bool event(QEvent *event) override; bool keyPressed(QKeyEvent* event); bool keyReleased(QKeyEvent* event); void paintEvent(QPaintEvent* event) override; void resizeEvent(QResizeEvent* event) override; void mousePressEvent(QMouseEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; void mouseDoubleClickEvent(QMouseEvent* event) override; bool mouseHoverd(QHoverEvent* event); void wheelEvent(QWheelEvent* event) override; void leaveEvent(QEvent*) override; // Midi private: static void midiThreadReceivedEvent(double delay, const uint8_t *msg, size_t len, void *userData); private slots: void midiKeyEvent(uchar status, uchar key, uchar velocity); private: QPixmap completePixmap_, backPixmap_, textPixmap_, forePixmap_, headerPixmap_; std::shared_ptr bt_; std::weak_ptr comStack_; std::shared_ptr config_; std::shared_ptr palette_; QFont stepFont_, headerFont_; QFont stepFontDef_, headerFontDef_; int stepFontWidth_, stepFontHeight_, stepFontAscent_, stepFontLeading_; int headerFontAscent_; int widthSpace_, widthSpaceDbl_; int stepNumWidthCnt_, stepNumWidth_, stepNumBase_; int baseTrackWidth_; int toneNameWidth_, instWidth_; int volWidth_; int effWidth_, effIDWidth_, effValWidth_; int tracksWidthFromLeftToEnd_; int hdMuteToggleWidth_, hdEffCompandButtonWidth_; int headerHeight_; int hdPlusY_, hdMinusY_; int curRowBaselineY_; int curRowY_; std::vector visTracks_, rightEffn_; int leftTrackVisIdx_; SongStyle songStyle_; int curSongNum_; PatternPosition curPos_, hovPos_; PatternPosition mousePressPos_, mouseReleasePos_; PatternPosition selLeftAbovePos_, selRightBelowPos_; PatternPosition shiftPressedPos_; PatternPosition doubleClickPos_; PatternPosition markerPos_; bool isIgnoreToSlider_, isIgnoreToOrder_; bool isPressedPlus_, isPressedMinus_; int entryCnt_; int selectAllState_; bool isMuteElse_; int hl1Cnt_, hl2Cnt_; int editableStepCnt_; int viewedRowCnt_; int viewedRegionHeight_; int viewedRowsHeight_, viewedRowOffset_, viewedCenterY_, viewedCenterBaseY_; PatternPosition viewedFirstPos_, viewedCenterPos_, viewedLastPos_; bool backChanged_, textChanged_, foreChanged_, headerChanged_, focusChanged_, followModeChanged_; bool hasFocussedBefore_; int stepDownCount_; std::atomic_bool repaintable_; // Recurrensive repaint guard std::atomic_int repaintingCnt_; std::atomic_bool isInitedFirstMod_; // Shortcuts QShortcut upSc_, upWSSc_, dnSc_, dnWSSc_, pgUpSc_, pgUpWSSc_, pgDnSc_, pgDnWSSc_; QShortcut homeSc_, homeWSSc_, endSc_, endWSSc_, hlUpSc_, hlUpWSSc_, hlDnSc_, hlDnWSSc_; QShortcut ltSc_, ltWSSc_, rtSc_, rtWSSc_; QShortcut keyOffSc_, keyCutSc_, echoBufSc_, stepMvUpSc_, stepMvDnSc_, expandColSc_, shrinkColSc_; // Meta methods int midiKeyEventMethod_; void funcResize(); void updateSizes(); void initDisplay(); void drawPattern(const QRect& rect); void drawRows(int maxWidth); void quickDrawRows(int maxWidth); /// Return: /// track width int drawStep(QPainter& forePainter, QPainter& textPainter, QPainter& backPainter, int trackVisIdx, int orderNum, int stepNum, int x, int baseY, int rowY); void drawHeaders(int maxWidth); void drawBorders(int maxWidth); void drawShadow(); // NOTE: Calculated by visible tracks int calculateTracksWidthWithRowNum(int beginIdx, int endIdx) const; int calculateColNumInRow(int trackVisIdx, int colNumInTrack, bool isExpanded = false) const; int calculateColumnDistance(int beginTrackIdx, int beginColumn, int endTrackIdx, int endColumn, bool isExpanded = false) const; int calculateStepDistance(int beginOrder, int beginStep, int endOrder, int endStep) const; PatternPosition calculatePositionFrom(int order, int step, int by) const; QPoint calculateCurrentCursorPosition() const; inline void updateTracksWidthFromLeftToEnd() { tracksWidthFromLeftToEnd_ = calculateTracksWidthWithRowNum( leftTrackVisIdx_, static_cast(visTracks_.size()) - 1); } void moveCursorToRight(int n); void moveViewToRight(int n); void moveCursorToDown(int n); inline void checkSelectionByCursorMove(bool isShift) { if (isShift) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); } bool enterToneData(QKeyEvent* event); void setStepKeyOn(const Note& note); bool enterInstrumentData(int key); void setStepInstrument(int num); bool enterVolumeData(int key); void setStepVolume(int volume); bool enterEffectID(int key); void setStepEffectID(QString str); bool enterEffectValue(int key); void setStepEffectValue(int value); inline int updateEntryCount() { entryCnt_ = (entryCnt_ + 1) % 2; return entryCnt_; } void insertStep(); void deletePreviousStep(); void eraseSelectedCells(); void pasteCopiedCells(const PatternPosition& cursorPos); void pasteMixCopiedCells(const PatternPosition& cursorPos); void pasteOverwriteCopiedCells(const PatternPosition& cursorPos); void pasteInsertCopiedCells(const PatternPosition& cursorPos); PatternPosition getPasteLeftAbovePosition( int pasteCol, const PatternPosition& cursorPos, size_t cellW) const; Vector2d makeCopiedCellsForPasteFull(const PatternPosition& laPos, const Vector2d& cells); void transposeNote(const PatternPosition& startPos, const PatternPosition& endPos, int semitone); void changeValuesInPattern(const PatternPosition& startPos, const PatternPosition& endPos, int value); void toggleTrack(int trackIdx); void soloTrack(int trackIdx); void setSelectedRectangle(const PatternPosition& start, const PatternPosition& end); bool isSelectedCell(int trackVisIdx, int colNum, int orderNum, int stepNum); void showPatternContextMenu(const PatternPosition& pos, const QPoint& point); }; #endif // PATTERN_EDITOR_PANEL_HPP BambooTracker-0.6.5/BambooTracker/gui/pattern_editor/pattern_position.hpp000066400000000000000000000107451476276175200267210ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef PATTERN_POSITION_HPP #define PATTERN_POSITION_HPP struct PatternPosition { int trackVisIdx, colInTrack, order, step; friend bool operator==(const PatternPosition& a, const PatternPosition& b); friend bool operator!=(const PatternPosition& a, const PatternPosition& b); void setCols(int trackVisIdx, int colInTrack); void setRows(int order, int step); int compareCols(const PatternPosition& b) const; int compareRows(const PatternPosition& b) const; bool isEqualCols(const PatternPosition& b) const; bool isEqualCols(const int trackVisIdx, const int colInTrack) const; bool isEqualRows(const PatternPosition& b) const; bool isEqualRows(const int order, const int step) const; static bool inRowRange(const PatternPosition& pos, const PatternPosition& begin, const PatternPosition& last) { return (begin.compareRows(pos) <= 0 && pos.compareRows(last) <= 0); } static bool inRowRange(const PatternPosition& srcBegin, const PatternPosition& srcLast, const PatternPosition& tgtBegin, const PatternPosition& tgtLast) { if (PatternPosition::inRowRange(srcBegin, tgtBegin, tgtLast)) return true; else if (PatternPosition::inRowRange(srcLast, tgtBegin, tgtLast)) return true; else return (srcBegin.compareRows(tgtBegin) < 0 && tgtLast.compareRows(srcLast)); } }; inline bool operator==(const PatternPosition& a, const PatternPosition& b) { return ((a.trackVisIdx == b.trackVisIdx) && (a.colInTrack == b.colInTrack) && (a.order == b.order) && (a.step == b.step)); } inline bool operator!=(const PatternPosition& a, const PatternPosition& b) { return !(a == b); } inline void PatternPosition::setCols(int track, int colInTrack) { this->trackVisIdx = track; this->colInTrack = colInTrack; } inline void PatternPosition::setRows(int order, int step) { this->order = order; this->step = step; } /// Return: /// -2: this->track < b.track /// -1: this->track == b.track && this->colInTrack < b.colInTrack /// 0: this->track == b.track && this->colInTrack == b.colInTrack /// 1: this->track == b.track && this->colInTrack > b.colInTrack /// 2: this->track > b.track inline int PatternPosition::compareCols(const PatternPosition& b) const { if (trackVisIdx < b.trackVisIdx) return -2; else if (trackVisIdx > b.trackVisIdx) return 2; else { if (colInTrack < b.colInTrack) return -1; else if (colInTrack > b.colInTrack) return 1; else return 0; } } /// Return: /// -2: this->order < b.order /// -1: this->order == b.order && this->step < b.step /// 0: *this == b /// 1: this->order == b.order && this->step > b.step /// 2: this->order > b.order inline int PatternPosition::compareRows(const PatternPosition& b) const { if (order < b.order) return -2; else if (order > b.order) return 2; else { if (step < b.step) return -1; else if (step > b.step) return 1; else return 0; } } inline bool PatternPosition::isEqualCols(const PatternPosition& b) const { return isEqualCols(b.trackVisIdx, b.colInTrack); } inline bool PatternPosition::isEqualCols(const int trackVisIdx, const int colInTrack) const { return (this->trackVisIdx == trackVisIdx && this->colInTrack == colInTrack); } inline bool PatternPosition::isEqualRows(const PatternPosition& b) const { return isEqualRows(b.order, b.step); } inline bool PatternPosition::isEqualRows(const int order, const int step) const { return (this->order == order && this->step == step); } #endif // PATTERN_POSITION_HPP BambooTracker-0.6.5/BambooTracker/gui/q_application_wrapper.cpp000066400000000000000000000031071476276175200246450ustar00rootroot00000000000000/* * Copyright (C) 2019-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "q_application_wrapper.hpp" #include #include QApplicationWrapper::QApplicationWrapper(int& argc, char** argv) : QApplication(argc, argv) {} bool QApplicationWrapper::notify(QObject* receiver, QEvent* event) { try { return QApplication::notify(receiver, event); } catch (std::exception& e) { QMessageBox::critical(nullptr, QObject::tr("Error"), QObject::tr("An unknown error occurred.\n%1").arg(e.what())); return false; } } BambooTracker-0.6.5/BambooTracker/gui/q_application_wrapper.hpp000066400000000000000000000026101476276175200246500ustar00rootroot00000000000000/* * Copyright (C) 2019 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef Q_APPLICATION_WRAPPER_HPP #define Q_APPLICATION_WRAPPER_HPP #include class QApplicationWrapper : public QApplication { public: QApplicationWrapper(int& argc, char** argv); bool notify(QObject* receiver, QEvent* event) override; }; #endif // Q_APPLICATION_WRAPPER_HPP BambooTracker-0.6.5/BambooTracker/gui/s98_export_settings_dialog.cpp000066400000000000000000000105231476276175200255450ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "s98_export_settings_dialog.hpp" #include "ui_s98_export_settings_dialog.h" #include "io/export_io.hpp" S98ExportSettingsDialog::S98ExportSettingsDialog(QWidget *parent) : QDialog(parent), ui(new Ui::S98ExportSettingsDialog) { ui->setupUi(this); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); for (QRadioButton *button : { ui->ym2608RadioButton, ui->ym2612RadioButton, ui->ym2203RadioButton, ui->noneFmRadioButton, ui->internalSsgRadioButton, ui->ay8910PsgRadioButton }) connect(button, &QAbstractButton::toggled, this, &S98ExportSettingsDialog::updateSupportInformation); updateSupportInformation(); } S98ExportSettingsDialog::~S98ExportSettingsDialog() { delete ui; } int S98ExportSettingsDialog::getResolution() const { return ui->resSpinBox->value(); } bool S98ExportSettingsDialog::enabledTag() const { return ui->tagGroupBox->isChecked(); } io::S98Tag S98ExportSettingsDialog::getS98Tag() const { io::S98Tag tag; tag.title = ui->titleLineEdit->text().toUtf8().toStdString(); tag.artist = ui->artistLineEdit->text().toUtf8().toStdString(); tag.game = ui->gameLineEdit->text().toUtf8().toStdString(); tag.year = ui->yearLineEdit->text().toUtf8().toStdString(); tag.genre = ui->genreLineEdit->text().toUtf8().toStdString(); tag.comment = ui->commentLineEdit->text().toUtf8().toStdString(); tag.copyright = ui->copyrightLineEdit->text().toUtf8().toStdString(); tag.s98by = ui->s98byLineEdit->text().toUtf8().toStdString(); tag.system = ui->systemLineEdit->text().toUtf8().toStdString(); return tag; } int S98ExportSettingsDialog::getExportTarget() const { int target = 0; if (ui->ym2608RadioButton->isChecked()) target |= io::Export_YM2608; else if (ui->ym2612RadioButton->isChecked()) target |= io::Export_YM2612; else if (ui->ym2203RadioButton->isChecked()) target |= io::Export_YM2203; if (ui->ay8910PsgRadioButton->isChecked()) target |= io::Export_AY8910Psg; else if (ui->ym2149PsgRadioButton->isChecked()) target |= io::Export_YM2149Psg; return target; } void S98ExportSettingsDialog::updateSupportInformation() { int target = getExportTarget(); int channels; int fm = target & io::Export_FmMask; int ssg = target & io::Export_SsgMask; switch (fm) { default: channels = 6; break; case io::Export_YM2203: channels = 3; break; case io::Export_NoneFm: channels = 0; break; } bool haveSsg = fm == io::Export_YM2608 || fm == io::Export_YM2203 || ssg != io::Export_InternalSsg; bool haveRhythm = fm == io::Export_YM2608; bool haveAdpcm = fm == io::Export_YM2608; ui->supportFmChannelsLabel->setText(QString::number(channels)); ui->supportSsgLabel->setText(haveSsg ? tr("Yes") : tr("No")); ui->supportRhythmLabel->setText(haveRhythm ? tr("Yes") : tr("No")); ui->supportAdpcmLabel->setText(haveAdpcm ? tr("Yes") : tr("No")); QPalette normalPalette = palette(); QPalette warnPalette = normalPalette; warnPalette.setColor(QPalette::WindowText, QColor(0xef2929)); ui->supportFmChannelsLabel->setPalette((channels == 6) ? normalPalette : warnPalette); ui->supportSsgLabel->setPalette(haveSsg ? normalPalette : warnPalette); ui->supportRhythmLabel->setPalette(haveRhythm ? normalPalette : warnPalette); ui->supportAdpcmLabel->setPalette(haveAdpcm ? normalPalette : warnPalette); } BambooTracker-0.6.5/BambooTracker/gui/s98_export_settings_dialog.hpp000066400000000000000000000032651476276175200255570ustar00rootroot00000000000000/* * Copyright (C) 2018-2019 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef S98_EXPORT_SETTINGS_DIALOG_HPP #define S98_EXPORT_SETTINGS_DIALOG_HPP #include #include "io/export_io.hpp" namespace Ui { class S98ExportSettingsDialog; } class S98ExportSettingsDialog : public QDialog { Q_OBJECT public: explicit S98ExportSettingsDialog(QWidget *parent = nullptr); ~S98ExportSettingsDialog(); int getResolution() const; bool enabledTag() const; io::S98Tag getS98Tag() const; int getExportTarget() const; private slots: void updateSupportInformation(); private: Ui::S98ExportSettingsDialog *ui; }; #endif // S98_EXPORT_SETTINGS_DIALOG_HPP BambooTracker-0.6.5/BambooTracker/gui/s98_export_settings_dialog.ui000066400000000000000000000302101476276175200253730ustar00rootroot00000000000000 S98ExportSettingsDialog 0 0 491 521 S98 export settings Resolution Hz 1 100000 1000 Tag true Title Artist Game Year Genre Comment Copyright S98by System NEC PC-9801 Target FM YM2608 OPNA true YM2612 OPN2 YM2203 OPN None SSG OPN internal true AY-3-8910 PSG YM2149 PSG Qt::Vertical 20 40 Support FM Channels QFrame::WinPanel QFrame::Sunken 6 Qt::PlainText Qt::AlignCenter SSG QFrame::WinPanel QFrame::Sunken Yes Qt::PlainText Qt::AlignCenter Rhythm QFrame::WinPanel QFrame::Sunken Yes Qt::PlainText Qt::AlignCenter ADPCM QFrame::WinPanel QFrame::Sunken Yes Qt::PlainText Qt::AlignCenter Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok resSpinBox tagGroupBox titleLineEdit artistLineEdit gameLineEdit yearLineEdit genreLineEdit commentLineEdit copyrightLineEdit s98byLineEdit systemLineEdit ym2608RadioButton ym2612RadioButton ym2203RadioButton noneFmRadioButton internalSsgRadioButton ay8910PsgRadioButton ym2149PsgRadioButton buttonBox accepted() S98ExportSettingsDialog accept() 248 254 157 274 buttonBox rejected() S98ExportSettingsDialog reject() 316 260 286 274 BambooTracker-0.6.5/BambooTracker/gui/slider_style.cpp000066400000000000000000000032421476276175200227640ustar00rootroot00000000000000/* * Copyright (C) 2018 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "slider_style.hpp" SliderStyle::SliderStyle(QStyle * style) : QProxyStyle(style) {} static SliderStyle* SLIDER_STYLE = nullptr; SliderStyle* SliderStyle::instance() { if (SLIDER_STYLE == nullptr) { SLIDER_STYLE = new SliderStyle(); } return SLIDER_STYLE; } int SliderStyle::styleHint (StyleHint hint, const QStyleOption* option, const QWidget* widget, QStyleHintReturn* returnData) const { if (hint == QStyle::SH_Slider_AbsoluteSetButtons) { return Qt::LeftButton; } else { return QProxyStyle::styleHint(hint, option, widget, returnData); } } BambooTracker-0.6.5/BambooTracker/gui/slider_style.hpp000066400000000000000000000032051476276175200227700ustar00rootroot00000000000000/* * Copyright (C) 2018 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef SLIDER_STYLE_HPP #define SLIDER_STYLE_HPP #include class SliderStyle : public QProxyStyle { private: SliderStyle(QStyle *style = nullptr); public: /// Get a global SliderStyle that lasts for the lifetime of the app. /// Can only be called a single thread (generally the main GUI thread). static SliderStyle* instance(); virtual int styleHint (StyleHint hint, const QStyleOption* option = nullptr, const QWidget* widget = nullptr, QStyleHintReturn* returnData = nullptr) const; }; #endif // SLIDER_STYLE_HPP BambooTracker-0.6.5/BambooTracker/gui/swap_tracks_dialog.cpp000066400000000000000000000036661476276175200241340ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "swap_tracks_dialog.hpp" #include "ui_swap_tracks_dialog.h" #include "song.hpp" #include #include "gui/gui_utils.hpp" SwapTracksDialog::SwapTracksDialog(const SongStyle& style, QWidget *parent) : QDialog(parent), ui(new Ui::SwapTracksDialog) { ui->setupUi(this); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); for (const auto& attrib : style.trackAttribs) { QString text = gui_utils::getTrackName(style.type, attrib.source, attrib.channelInSource); ui->track1ComboBox->addItem(text, attrib.number); ui->track2ComboBox->addItem(text, attrib.number); } } SwapTracksDialog::~SwapTracksDialog() { delete ui; } int SwapTracksDialog::getTrack1() const { return ui->track1ComboBox->currentData().toInt(); } int SwapTracksDialog::getTrack2() const { return ui->track2ComboBox->currentData().toInt(); } BambooTracker-0.6.5/BambooTracker/gui/swap_tracks_dialog.hpp000066400000000000000000000030301476276175200241220ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef SWAP_TRACKS_DIALOG_HPP #define SWAP_TRACKS_DIALOG_HPP #include struct SongStyle; namespace Ui { class SwapTracksDialog; } class SwapTracksDialog : public QDialog { Q_OBJECT public: explicit SwapTracksDialog(const SongStyle& style, QWidget *parent = nullptr); ~SwapTracksDialog() override; int getTrack1() const; int getTrack2() const; private: Ui::SwapTracksDialog *ui; }; #endif // SWAP_TRACKS_DIALOG_HPP BambooTracker-0.6.5/BambooTracker/gui/swap_tracks_dialog.ui000066400000000000000000000041021476276175200237510ustar00rootroot00000000000000 SwapTracksDialog 0 0 174 95 Swap Tracks Track 1 Track 2 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() SwapTracksDialog accept() 248 254 157 274 buttonBox rejected() SwapTracksDialog reject() 316 260 286 274 BambooTracker-0.6.5/BambooTracker/gui/transpose_song_dialog.cpp000066400000000000000000000044061476276175200246500ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "transpose_song_dialog.hpp" #include "ui_transpose_song_dialog.h" #include TransposeSongDialog::TransposeSongDialog(QWidget *parent) : QDialog(parent), ui(new Ui::TransposeSongDialog) { ui->setupUi(this); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); for (int i = 0; i < 128; ++i) { auto item = new QListWidgetItem(QString("%1").arg(i, 2, 16, QChar('0')).toUpper()); checks[i] = item; item->setCheckState(Qt::Unchecked); QObject::connect(ui->reversePushButton, &QPushButton::clicked, this, [item] { item->setCheckState((item->checkState() == Qt::Unchecked) ? Qt::Checked : Qt::Unchecked); }); QObject::connect(ui->clearPushButton, &QPushButton::clicked, this, [item] { item->setCheckState(Qt::Unchecked); }); ui->listWidget->addItem(item); } } TransposeSongDialog::~TransposeSongDialog() { delete ui; } int TransposeSongDialog::getTransposeSemitones() const { return ui->spinBox->value(); } std::vector TransposeSongDialog::getExcludeInstruments() const { std::vector list; for (int i = 0; i < 128; ++i) { if (checks[i]->checkState() == Qt::Checked) list.push_back(i); } return list; } BambooTracker-0.6.5/BambooTracker/gui/transpose_song_dialog.hpp000066400000000000000000000031661476276175200246570ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef TRANSPOSE_SONG_DIALOG_HPP #define TRANSPOSE_SONG_DIALOG_HPP #include #include #include namespace Ui { class TransposeSongDialog; } class TransposeSongDialog : public QDialog { Q_OBJECT public: explicit TransposeSongDialog(QWidget *parent = nullptr); ~TransposeSongDialog() override; int getTransposeSemitones() const; std::vector getExcludeInstruments() const; private: Ui::TransposeSongDialog *ui; QListWidgetItem* checks[128]; }; #endif // TRANSPOSE_SONG_DIALOG_HPP BambooTracker-0.6.5/BambooTracker/gui/transpose_song_dialog.ui000066400000000000000000000114231476276175200245000ustar00rootroot00000000000000 TransposeSongDialog 0 0 300 300 Transpose Song Semitones -96 96 Qt::Horizontal 40 20 Exclude these instruments Qt::Horizontal 40 20 Qt::Horizontal 40 20 Qt::Horizontal 40 20 Reverse Clear All Qt::ElideNone QListView::TopToBottom true QListView::Adjust 1 true Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok spinBox listWidget reversePushButton clearPushButton buttonBox accepted() TransposeSongDialog accept() 248 254 157 274 buttonBox rejected() TransposeSongDialog reject() 316 260 286 274 BambooTracker-0.6.5/BambooTracker/gui/vgm_export_settings_dialog.cpp000066400000000000000000000164761476276175200257300ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "vgm_export_settings_dialog.hpp" #include "ui_vgm_export_settings_dialog.h" #include #include "io/export_io.hpp" VgmExportSettingsDialog::VgmExportSettingsDialog(QWidget *parent) : QDialog(parent), ui(new Ui::VgmExportSettingsDialog) { ui->setupUi(this); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); for (QRadioButton *button : { ui->ym2608RadioButton, ui->ym2612RadioButton, ui->ym2203RadioButton, ui->noneFmRadioButton, ui->internalSsgRadioButton, ui->ay8910PsgRadioButton }) connect(button, &QAbstractButton::toggled, this, &VgmExportSettingsDialog::updateSupportInformation); updateSupportInformation(); } VgmExportSettingsDialog::~VgmExportSettingsDialog() { delete ui; } bool VgmExportSettingsDialog::enabledGD3() const { return ui->gd3GroupBox->isChecked(); } QString VgmExportSettingsDialog::getTrackNameEnglish() const { return ui->titleEnLineEdit->text(); } QString VgmExportSettingsDialog::getTrackNameJapanese() const { return ui->titleJpLineEdit->text(); } QString VgmExportSettingsDialog::getGameNameEnglish() const { return ui->nameEnLineEdit->text(); } QString VgmExportSettingsDialog::getGameNameJapanese() const { return ui->nameJpLineEdit->text(); } QString VgmExportSettingsDialog::getSystemNameEnglish() const { return ui->systemEnLineEdit->text(); } QString VgmExportSettingsDialog::getSystemNameJapanese() const { return ui->systemJpLineEdit->text(); } QString VgmExportSettingsDialog::getTrackAuthorEnglish() const { return ui->authorEnLineEdit->text(); } QString VgmExportSettingsDialog::getTrackAuthorJapanese() const { return ui->authorJpLineEdit->text(); } QString VgmExportSettingsDialog::getReleaseDate() const { return ui->releaseDateLineEdit->text(); } QString VgmExportSettingsDialog::getVgmCreator() const { return ui->creatorLineEdit->text(); } QString VgmExportSettingsDialog::getNotes() const { return ui->notesPlainTextEdit->toPlainText(); } io::GD3Tag VgmExportSettingsDialog::getGD3Tag() const { io::GD3Tag tag; QTextCodec* sjis = QTextCodec::codecForName("Shift-JIS"); std::string endNull = ""; endNull += '\0'; endNull += '\0'; const QByteArray trackNameEn = getTrackNameEnglish().toLatin1(); tag.trackNameEn = ""; for (const auto& c : trackNameEn) { tag.trackNameEn += c; tag.trackNameEn += '\0'; } tag.trackNameEn += endNull; const QByteArray trackNameJp = sjis->fromUnicode(getTrackNameJapanese()); tag.trackNameJp = ""; for (const auto& c : trackNameJp) { tag.trackNameJp += c; } tag.trackNameJp += endNull; const QByteArray gameNameEn = getGameNameEnglish().toLatin1(); tag.gameNameEn = ""; for (const auto& c : gameNameEn) { tag.gameNameEn += c; tag.gameNameEn += '\0'; } tag.gameNameEn += endNull; const QByteArray gameNameJp = sjis->fromUnicode(getGameNameJapanese()); tag.gameNameJp = ""; for (const auto& c : gameNameJp) { tag.gameNameJp += c; } tag.gameNameJp += endNull; const QByteArray systemNameEn = getSystemNameEnglish().toLatin1(); tag.systemNameEn = ""; for (const auto& c : systemNameEn) { tag.systemNameEn += c; tag.systemNameEn += '\0'; } tag.systemNameEn += endNull; const QByteArray systemNameJp = sjis->fromUnicode(getSystemNameJapanese()); tag.systemNameJp = ""; for (const auto& c : systemNameJp) { tag.systemNameJp += c; } tag.systemNameJp += endNull; const QByteArray authorEn = getTrackAuthorEnglish().toLatin1(); tag.authorEn = ""; for (const auto& c : authorEn) { tag.authorEn += c; tag.authorEn += '\0'; } tag.authorEn += endNull; const QByteArray authorJp = sjis->fromUnicode(getTrackAuthorJapanese()); tag.authorJp = ""; for (const auto& c : authorJp) { tag.authorJp += c; } tag.authorJp += endNull; const QByteArray releaseDate = getReleaseDate().toLatin1(); tag.releaseDate = ""; for (const auto& c : releaseDate) { tag.releaseDate += c; tag.releaseDate += '\0'; } tag.releaseDate += endNull; const QByteArray vgmCreator = getVgmCreator().toLatin1(); tag.vgmCreator = ""; for (const auto& c : vgmCreator) { tag.vgmCreator += c; tag.vgmCreator += '\0'; } tag.vgmCreator += endNull; const QByteArray notes = getNotes().toLatin1(); tag.notes = ""; for (const auto& c : notes) { tag.notes += c; tag.notes += '\0'; } tag.notes += endNull; return tag; } int VgmExportSettingsDialog::getExportTarget() const { int target = 0; if (ui->ym2608RadioButton->isChecked()) target |= io::Export_YM2608; else if (ui->ym2612RadioButton->isChecked()) target |= io::Export_YM2612; else if (ui->ym2203RadioButton->isChecked()) target |= io::Export_YM2203; else if (ui->ym2610bRadioButton->isChecked()) target |= io::Export_YM2610B; if (ui->ay8910PsgRadioButton->isChecked()) target |= io::Export_AY8910Psg; else if (ui->ym2149PsgRadioButton->isChecked()) target |= io::Export_YM2149Psg; return target; } bool VgmExportSettingsDialog::isEnabledMix() const { return ui->mixGroupBox->isChecked(); } double VgmExportSettingsDialog::getGain() const { return ui->gainDoubleSpinBox->value(); } void VgmExportSettingsDialog::updateSupportInformation() { int target = getExportTarget(); int channels; int fm = target & io::Export_FmMask; int ssg = target & io::Export_SsgMask; switch (fm) { default: channels = 6; break; case io::Export_YM2203: channels = 3; break; case io::Export_NoneFm: channels = 0; break; } bool haveSsg = fm == io::Export_YM2608 || fm == io::Export_YM2203 || fm == io::Export_YM2610B || ssg != io::Export_InternalSsg; bool haveRhythm = fm == io::Export_YM2608; bool haveAdpcm = fm == io::Export_YM2608; ui->supportFmChannelsLabel->setText(QString::number(channels)); ui->supportSsgLabel->setText(haveSsg ? tr("Yes") : tr("No")); ui->supportRhythmLabel->setText(haveRhythm ? tr("Yes") : tr("No")); ui->supportAdpcmLabel->setText(haveAdpcm ? tr("Yes") : tr("No")); QPalette normalPalette = palette(); QPalette warnPalette = normalPalette; warnPalette.setColor(QPalette::WindowText, QColor(0xef2929)); ui->supportFmChannelsLabel->setPalette((channels == 6) ? normalPalette : warnPalette); ui->supportSsgLabel->setPalette(haveSsg ? normalPalette : warnPalette); ui->supportRhythmLabel->setPalette(haveRhythm ? normalPalette : warnPalette); ui->supportAdpcmLabel->setPalette(haveAdpcm ? normalPalette : warnPalette); } BambooTracker-0.6.5/BambooTracker/gui/vgm_export_settings_dialog.hpp000066400000000000000000000041641476276175200257240ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef VGM_EXPORT_SETTINGS_DIALOG_HPP #define VGM_EXPORT_SETTINGS_DIALOG_HPP #include #include #include "io/export_io.hpp" namespace Ui { class VgmExportSettingsDialog; } class VgmExportSettingsDialog : public QDialog { Q_OBJECT public: explicit VgmExportSettingsDialog(QWidget *parent = nullptr); ~VgmExportSettingsDialog(); bool enabledGD3() const; QString getTrackNameEnglish() const; QString getTrackNameJapanese() const; QString getGameNameEnglish() const; QString getGameNameJapanese() const; QString getSystemNameEnglish() const; QString getSystemNameJapanese() const; QString getTrackAuthorEnglish() const; QString getTrackAuthorJapanese() const; QString getReleaseDate() const; QString getVgmCreator() const; QString getNotes() const; io::GD3Tag getGD3Tag() const; int getExportTarget() const; bool isEnabledMix() const; double getGain() const; private slots: void updateSupportInformation(); private: Ui::VgmExportSettingsDialog *ui; }; #endif // VGM_EXPORT_SETTINGS_DIALOG_HPP BambooTracker-0.6.5/BambooTracker/gui/vgm_export_settings_dialog.ui000066400000000000000000000515361476276175200255570ustar00rootroot00000000000000 VgmExportSettingsDialog 0 0 507 685 VGM export settings GD3 tag true 6 6 6 6 3 Game 0 0 70 0 70 16777215 Name English 0 0 70 0 70 16777215 Release date Qt::Horizontal 152 20 Japanese 0 0 70 0 70 16777215 System Japanese NEC PC-9801 English Track 0 0 70 0 70 16777215 Title English Japanese 0 0 70 0 70 16777215 Author English Japanese VGM file 0 0 70 0 70 16777215 Creator Qt::Horizontal 149 20 0 0 70 0 70 16777215 Notes Target FM YM2608 OPNA true YM2612 OPN2 YM2203 OPN YM2610B OPNB None SSG OPN internal true AY-3-8910 PSG YM2149 PSG Mix true false Gain Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter dB 1 -10.000000000000000 10.000000000000000 0.100000000000000 -1.000000000000000 Qt::Vertical 20 40 Support FM Channels QFrame::WinPanel QFrame::Sunken 6 Qt::PlainText Qt::AlignCenter SSG QFrame::WinPanel QFrame::Sunken Yes Qt::PlainText Qt::AlignCenter Rhythm QFrame::WinPanel QFrame::Sunken Yes Qt::PlainText Qt::AlignCenter ADPCM QFrame::WinPanel QFrame::Sunken Yes Qt::PlainText Qt::AlignCenter Qt::Vertical 20 40 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok gd3GroupBox titleEnLineEdit titleJpLineEdit authorEnLineEdit authorJpLineEdit nameEnLineEdit nameJpLineEdit systemEnLineEdit systemJpLineEdit releaseDateLineEdit creatorLineEdit notesPlainTextEdit ym2608RadioButton ym2612RadioButton ym2203RadioButton ym2610bRadioButton noneFmRadioButton internalSsgRadioButton ay8910PsgRadioButton ym2149PsgRadioButton buttonBox accepted() VgmExportSettingsDialog accept() 248 254 157 274 buttonBox rejected() VgmExportSettingsDialog reject() 316 260 286 274 BambooTracker-0.6.5/BambooTracker/gui/wave_export_settings_dialog.cpp000066400000000000000000000070011476276175200260610ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "wave_export_settings_dialog.hpp" #include "ui_wave_export_settings_dialog.h" #include #include #include "gui/gui_utils.hpp" #include "song.hpp" WaveExportSettingsDialog::WaveExportSettingsDialog(const std::vector defUnmutes, QWidget *parent) : QDialog(parent), ui(new Ui::WaveExportSettingsDialog) { ui->setupUi(this); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); constexpr uint32_t SAMPLE_RATES[] = { 8000, 16000, 22050, 32000, 44100, 48000, 55466, 96000, 192000, 249600 }; constexpr uint32_t DEFAULT_RATE = 44100; for (auto rate : SAMPLE_RATES) { ui->sampleRateComboBox->addItem(QString("%1Hz").arg(rate), rate); if (rate == DEFAULT_RATE) { ui->sampleRateComboBox->setCurrentIndex(ui->sampleRateComboBox->count() - 1); } } struct Pair { SoundSource src; int count; }; static const QList SRC_NUMS = { { SoundSource::FM, 6 }, { SoundSource::SSG, 3 }, { SoundSource::RHYTHM, 6 }, { SoundSource::ADPCM, 1 } }; int tmpT = 0; for (const auto& pair : SRC_NUMS) { for (int i = 0; i < pair.count; ++i, ++tmpT) { auto item = new QListWidgetItem(gui_utils::getTrackName(SongType::Standard, pair.src, i), ui->tracksListWidget); item->setCheckState(std::any_of(defUnmutes.begin(), defUnmutes.end(), [tmpT](const int t) { return t == tmpT; }) ? Qt::Checked : Qt::Unchecked); } } } WaveExportSettingsDialog::~WaveExportSettingsDialog() { delete ui; } int WaveExportSettingsDialog::getSampleRate() const { return ui->sampleRateComboBox->currentData().toInt(); } int WaveExportSettingsDialog::getLoopCount() const { return ui->loopSpinBox->value(); } std::vector WaveExportSettingsDialog::getSoloExportTracks() const { if (!ui->tracksGroupBox->isChecked()) return {}; std::vector tracks; for (int i = 0; i < ui->tracksListWidget->count(); ++i) { if (ui->tracksListWidget->item(i)->checkState() == Qt::Checked) tracks.push_back(i); } return tracks; } void WaveExportSettingsDialog::on_reversePushButton_clicked() { for (int i = 0; i < ui->tracksListWidget->count(); ++i) { auto item = ui->tracksListWidget->item(i); item->setCheckState((item->checkState() == Qt::Checked) ? Qt::Unchecked : Qt::Checked); } } void WaveExportSettingsDialog::on_allPushButton_clicked() { for (int i = 0; i < ui->tracksListWidget->count(); ++i) { auto item = ui->tracksListWidget->item(i); item->setCheckState(Qt::Checked); } } BambooTracker-0.6.5/BambooTracker/gui/wave_export_settings_dialog.hpp000066400000000000000000000033701476276175200260730ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef WAVE_EXPORT_SETTINGS_DIALOG_HPP #define WAVE_EXPORT_SETTINGS_DIALOG_HPP #include #include namespace Ui { class WaveExportSettingsDialog; } class WaveExportSettingsDialog : public QDialog { Q_OBJECT public: explicit WaveExportSettingsDialog(const std::vector defUnmutes, QWidget *parent = nullptr); ~WaveExportSettingsDialog() override; int getSampleRate() const; int getLoopCount() const; std::vector getSoloExportTracks() const; private slots: void on_reversePushButton_clicked(); void on_allPushButton_clicked(); private: Ui::WaveExportSettingsDialog *ui; }; #endif // WAVE_EXPORT_SETTINGS_DIALOG_HPP BambooTracker-0.6.5/BambooTracker/gui/wave_export_settings_dialog.ui000066400000000000000000000064301476276175200257210ustar00rootroot00000000000000 WaveExportSettingsDialog 0 0 239 300 WAV export settings Loop 1 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok Sample rate Separate track export true false Reverse Check All sampleRateComboBox loopSpinBox tracksGroupBox tracksListWidget reversePushButton allPushButton buttonBox accepted() WaveExportSettingsDialog accept() 203 120 157 131 buttonBox rejected() WaveExportSettingsDialog reject() 203 120 214 131 BambooTracker-0.6.5/BambooTracker/gui/wave_visual.cpp000066400000000000000000000054301476276175200226100ustar00rootroot00000000000000/* * Copyright (C) 2019-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "gui/wave_visual.hpp" #include "gui/color_palette.hpp" #include #include //Xcode 8.3: "no member names 'abs' in namespace 'std' #include WaveVisual::WaveVisual(QWidget *parent) : QWidget(parent) { setAttribute(Qt::WA_OpaquePaintEvent); } void WaveVisual::setColorPalette(std::shared_ptr palette) { palette_ = palette; } void WaveVisual::setStereoSamples(const int16_t *buffer, size_t frames) { // identify the highest of the 2 input signals int32_t sum = 0; for (size_t i = 0; i < frames; ++i) { size_t p = i << 1; sum += std::abs(buffer[p]); sum -= std::abs(buffer[p + 1]); } // use this signal as display data samples_.resize(frames); int16_t *samples = samples_.data(); for (size_t i = 0; i < frames; ++i) samples[i] = buffer[(i << 1) + (sum < 0)]; repaint(); } void WaveVisual::paintEvent(QPaintEvent*) { QPainter painter(this); if (!palette_) return; int w = width(); int h = height(); int centerh = h >> 1; painter.fillRect(0, 0, w, h, palette_->wavBackColor); const int16_t *samples = samples_.data(); size_t frames = samples_.size(); if (frames <= 0) return; painter.setPen(palette_->wavDrawColor); int lastY = centerh; const int16_t range = std::numeric_limits::max() >> 1; for (int x = 0; x < w; ++x) { size_t index = (size_t)(x * ((double)frames / w)); int16_t sample = samples[index]; int y = centerh - (centerh * sample / range); painter.drawPoint(x, y); // draw intermediate points int y1 = lastY, y2 = y; int yM = (y1 + y2) >> 1; while (y1 != y2) { bool b = (y1 > yM) ^ (y1 > y2); painter.drawPoint(x - !b, y1); y1 += (y1 < y2) ? 1 : -1; } lastY = y; } } BambooTracker-0.6.5/BambooTracker/gui/wave_visual.hpp000066400000000000000000000032011476276175200226070ustar00rootroot00000000000000/* * Copyright (C) 2019-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef WAVE_VISUAL_HPP #define WAVE_VISUAL_HPP #include #include #include #include class ColorPalette; class WaveVisual : public QWidget { Q_OBJECT public: explicit WaveVisual(QWidget *parent = nullptr); void setColorPalette(std::shared_ptr palette); void setStereoSamples(const int16_t *buffer, size_t frames); protected: void paintEvent(QPaintEvent*) override; private: std::shared_ptr palette_; std::vector samples_; }; #endif // WAVE_VISUAL_HPP BambooTracker-0.6.5/BambooTracker/gui/wheel_spin_box.cpp000066400000000000000000000006551476276175200232740ustar00rootroot00000000000000#include "wheel_spin_box.hpp" #include WheelSpinBox::WheelSpinBox(QWidget * parent) : QSpinBox(parent) { // Prevent mouse scrolling from focusing the QSpinBox. QSpinBox::setFocusPolicy(Qt::StrongFocus); } void WheelSpinBox::stepBy(int steps) { QSpinBox::stepBy(steps); // Prevent mouse scrolling from permanently selecting the QSpinBox. if (!QSpinBox::hasFocus()) { QSpinBox::lineEdit()->deselect(); } } BambooTracker-0.6.5/BambooTracker/gui/wheel_spin_box.hpp000066400000000000000000000002541476276175200232740ustar00rootroot00000000000000#pragma once #include class WheelSpinBox : public QSpinBox { public: explicit WheelSpinBox(QWidget * parent = nullptr); void stepBy(int steps) override; }; BambooTracker-0.6.5/BambooTracker/instrument/000077500000000000000000000000001476276175200212015ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/instrument/abstract_instrument_property.cpp000066400000000000000000000032071476276175200277460ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "abstract_instrument_property.hpp" AbstractInstrumentProperty::AbstractInstrumentProperty(int num) : num_(num) { } void AbstractInstrumentProperty::registerUserInstrument(int instNum) { users_.insert(instNum); } void AbstractInstrumentProperty::deregisterUserInstrument(int instNum) { auto&& it = users_.find(instNum); if (it != users_.end()) users_.erase(it); } bool AbstractInstrumentProperty::isUserInstrument() const { return !users_.empty(); } void AbstractInstrumentProperty::clearUserInstruments() { users_.clear(); } BambooTracker-0.6.5/BambooTracker/instrument/abstract_instrument_property.hpp000066400000000000000000000033651476276175200277600ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include class AbstractInstrumentProperty { public: virtual ~AbstractInstrumentProperty() = default; inline void setNumber(int num) noexcept { num_ = num; } inline int getNumber() const noexcept { return num_; } void registerUserInstrument(int instNum); void deregisterUserInstrument(int instNum); bool isUserInstrument() const; inline std::multiset getUserInstruments() const noexcept { return users_; } void clearUserInstruments(); virtual bool isEdited() const = 0; virtual void clearParameters() = 0; protected: explicit AbstractInstrumentProperty(int num); private: int num_; std::multiset users_; }; BambooTracker-0.6.5/BambooTracker/instrument/bank.cpp000066400000000000000000000231341476276175200226230ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "bank.hpp" #include #include "io/instrument_io.hpp" #include "io/opni_io.hpp" #include "io/btb_io.hpp" #include "io/ff_io.hpp" #include "io/ppc_io.hpp" #include "io/p86_io.hpp" #include "io/pps_io.hpp" #include "io/pvi_io.hpp" #include "io/pzi_io.hpp" #include "io/dat_io.hpp" #include "io/pmb_io.hpp" #include "format/wopn_file.h" BtBank::BtBank(const std::vector& ids, const std::vector& names) : ids_(ids), names_(names) { } BtBank::BtBank(const std::vector& ids, const std::vector& names, const std::vector& instSecs, const io::BinaryContainer& propSec, uint32_t version) : instCtrs_(instSecs), propCtr_(propSec), ids_(ids), names_(names), version_(version) { } size_t BtBank::getNumInstruments() const { return ids_.size(); } std::string BtBank::getInstrumentIdentifier(size_t index) const { return std::to_string(ids_.at(index)); } std::string BtBank::getInstrumentName(size_t index) const { return names_.at(index); } AbstractInstrument* BtBank::loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const { return io::BtbIO::loadInstrument(instCtrs_.at(static_cast(index)), propCtr_, instMan, instNum, version_); } /******************************/ void WopnBank::WOPNDeleter::operator()(WOPNFile *x) { WOPN_Free(x); } struct WopnBank::InstEntry { WOPNInstrument *inst; struct ValuesType { bool percussive : 1; unsigned msb : 7; unsigned lsb : 7; unsigned nth : 7; } vals; }; WopnBank::WopnBank(WOPNFile* wopn) : wopn_(wopn) { unsigned numM = wopn->banks_count_melodic; unsigned numP = wopn->banks_count_percussion; size_t instMax = 128 * (numP + numM); entries_.reserve(instMax); for (size_t i = 0; i < instMax; ++i) { InstEntry ent; ent.vals.percussive = (i / 128) >= numM; WOPNBank& bank = ent.vals.percussive ? wopn->banks_percussive[(i / 128) - numM] : wopn->banks_melodic[i / 128]; ent.vals.msb = bank.bank_midi_msb; ent.vals.lsb = bank.bank_midi_lsb; ent.vals.nth = i % 128; ent.inst = &bank.ins[ent.vals.nth]; if ((ent.inst->inst_flags & WOPN_Ins_IsBlank) == 0) entries_.push_back(ent); } entries_.shrink_to_fit(); } size_t WopnBank::getNumInstruments() const { return entries_.size(); } std::string WopnBank::getInstrumentIdentifier(size_t index) const { const InstEntry& ent = entries_.at(index); char identifier[64]; std::sprintf(identifier, "%c%03d:%03d:%03d", "MP"[ent.vals.percussive], ent.vals.msb, ent.vals.lsb, ent.vals.nth); return identifier; } std::string WopnBank::getInstrumentName(size_t index) const { const InstEntry& ent = entries_.at(index); return ent.inst->inst_name; } AbstractInstrument* WopnBank::loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const { const InstEntry& ent = entries_.at(index); return io::OpniIO::loadWOPNInstrument(*ent.inst, instMan, instNum); } /******************************/ FfBank::FfBank(const std::vector& ids, const std::vector& names, const std::vector& ctrs) : ids_(ids), names_(names), instCtrs_(ctrs) { } size_t FfBank::getNumInstruments() const { return ids_.size(); } std::string FfBank::getInstrumentIdentifier(size_t index) const { return std::to_string(ids_.at(index)); } std::string FfBank::getInstrumentName(size_t index) const { return names_.at(index); } AbstractInstrument* FfBank::loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const { return io::FfIO::loadInstrument(instCtrs_.at(index), names_.at(index), instMan, instNum); } void FfBank::setInstrumentName(size_t index, const std::string& name) { names_.at(index) = name; } /******************************/ PpcBank::PpcBank(const std::vector& ids, const std::vector>& samples) : ids_(ids), samples_(samples) { } size_t PpcBank::getNumInstruments() const { return samples_.size(); } std::string PpcBank::getInstrumentIdentifier(size_t index) const { return std::to_string(ids_.at(index)); } std::string PpcBank::getInstrumentName(size_t) const { return ""; } AbstractInstrument* PpcBank::loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const { return io::PpcIO::loadInstrument(samples_.at(index), instMan, instNum); } /******************************/ P86Bank::P86Bank(const std::vector& ids, const std::vector>& samples) : ids_(ids), samples_(samples) { } size_t P86Bank::getNumInstruments() const { return samples_.size(); } std::string P86Bank::getInstrumentIdentifier(size_t index) const { return std::to_string(ids_.at(index)); } std::string P86Bank::getInstrumentName(size_t) const { return ""; } AbstractInstrument* P86Bank::loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const { return io::P86IO::loadInstrument(samples_.at(index), instMan, instNum); } /******************************/ PpsBank::PpsBank(const std::vector& ids, const std::vector>& samples) : ids_(ids), samples_(samples) { } size_t PpsBank::getNumInstruments() const { return samples_.size(); } std::string PpsBank::getInstrumentIdentifier(size_t index) const { return std::to_string(ids_.at(index)); } std::string PpsBank::getInstrumentName(size_t) const { return ""; } AbstractInstrument* PpsBank::loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const { return io::PpsIO::loadInstrument(samples_.at(index), instMan, instNum); } /******************************/ PviBank::PviBank(const std::vector& ids, uint16_t deltaN, const std::vector>& samples) : ids_(ids), deltaN_(deltaN), samples_(samples) { } size_t PviBank::getNumInstruments() const { return samples_.size(); } std::string PviBank::getInstrumentIdentifier(size_t index) const { return std::to_string(ids_.at(index)); } std::string PviBank::getInstrumentName(size_t) const { return ""; } AbstractInstrument* PviBank::loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const { return io::PviIO::loadInstrument(samples_.at(index), deltaN_, instMan, instNum); } /******************************/ PziBank::PziBank(const std::vector& ids, const std::vector& deltaNs, const std::vector& isRepeatedList, const std::vector>& samples) : ids_(ids), deltaNs_(deltaNs), isRepeatedList_(isRepeatedList), samples_(samples) { } size_t PziBank::getNumInstruments() const { return samples_.size(); } std::string PziBank::getInstrumentIdentifier(size_t index) const { return std::to_string(ids_.at(index)); } std::string PziBank::getInstrumentName(size_t) const { return ""; } AbstractInstrument* PziBank::loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const { return io::PziIO::loadInstrument(samples_.at(index), deltaNs_.at(index), isRepeatedList_.at(index), instMan, instNum); } /******************************/ Mucom88Bank::Mucom88Bank(const std::vector& ids, const std::vector& names, const std::vector& ctrs) : ids_(ids), names_(names), instCtrs_(ctrs) { } size_t Mucom88Bank::getNumInstruments() const { return ids_.size(); } std::string Mucom88Bank::getInstrumentIdentifier(size_t index) const { return std::to_string(ids_.at(index)); } std::string Mucom88Bank::getInstrumentName(size_t index) const { return names_.at(index); } AbstractInstrument* Mucom88Bank::loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const { return io::DatIO::loadInstrument(instCtrs_.at(index), names_.at(index), instMan, instNum); } void Mucom88Bank::setInstrumentName(size_t index, const std::string& name) { names_.at(index) = name; } /******************************/ PmbBank::PmbBank(const std::vector& ids, const std::vector& names, const std::vector>& samples) : ids_(ids), names_(names), samples_(samples) { } size_t PmbBank::getNumInstruments() const { return ids_.size(); } std::string PmbBank::getInstrumentIdentifier(size_t index) const { return std::to_string(ids_.at(index)); } std::string PmbBank::getInstrumentName(size_t index) const { return names_.at(index); } AbstractInstrument* PmbBank::loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const { return io::PmbIO::loadInstrument(samples_.at(index), names_.at(index), instMan, instNum); } void PmbBank::setInstrumentName(size_t index, const std::string& name) { names_.at(index) = name; } BambooTracker-0.6.5/BambooTracker/instrument/bank.hpp000066400000000000000000000170451476276175200226340ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include "io/binary_container.hpp" class AbstractInstrument; class InstrumentsManager; struct WOPNFile; class AbstractBank { public: virtual ~AbstractBank() = default; virtual size_t getNumInstruments() const = 0; virtual std::string getInstrumentIdentifier(size_t index) const = 0; virtual std::string getInstrumentName(size_t index) const = 0; virtual AbstractInstrument* loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const = 0; }; class BtBank final : public AbstractBank { public: BtBank(const std::vector& ids, const std::vector& names); BtBank(const std::vector& ids, const std::vector& names, const std::vector& instSecs, const io::BinaryContainer& propSec, uint32_t version); size_t getNumInstruments() const override; std::string getInstrumentIdentifier(size_t index) const override; std::string getInstrumentName(size_t index) const override; AbstractInstrument* loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const override; private: std::vector instCtrs_; io::BinaryContainer propCtr_; std::vector ids_; std::vector names_; uint32_t version_; }; class WopnBank final : public AbstractBank { public: explicit WopnBank(WOPNFile *wopn); size_t getNumInstruments() const override; std::string getInstrumentIdentifier(size_t index) const override; std::string getInstrumentName(size_t index) const override; AbstractInstrument* loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const override; private: struct WOPNDeleter { void operator()(WOPNFile *x); }; std::unique_ptr wopn_; struct InstEntry; std::vector entries_; }; class FfBank final : public AbstractBank { public: FfBank(const std::vector& ids, const std::vector& names, const std::vector& ctrs); size_t getNumInstruments() const override; std::string getInstrumentIdentifier(size_t index) const override; std::string getInstrumentName(size_t index) const override; AbstractInstrument* loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const override; void setInstrumentName(size_t index, const std::string& name); private: std::vector ids_; std::vector names_; std::vector instCtrs_; }; class PpcBank final : public AbstractBank { public: PpcBank(const std::vector& ids, const std::vector>& samples); size_t getNumInstruments() const override; std::string getInstrumentIdentifier(size_t index) const override; std::string getInstrumentName(size_t) const override; AbstractInstrument* loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const override; private: std::vector ids_; std::vector> samples_; }; class P86Bank final : public AbstractBank { public: P86Bank(const std::vector& ids, const std::vector>& samples); size_t getNumInstruments() const override; std::string getInstrumentIdentifier(size_t index) const override; std::string getInstrumentName(size_t) const override; AbstractInstrument* loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const override; private: std::vector ids_; std::vector> samples_; }; class PpsBank final : public AbstractBank { public: PpsBank(const std::vector& ids, const std::vector>& samples); size_t getNumInstruments() const override; std::string getInstrumentIdentifier(size_t index) const override; std::string getInstrumentName(size_t) const override; AbstractInstrument* loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const override; private: std::vector ids_; std::vector> samples_; }; class PviBank final : public AbstractBank { public: PviBank(const std::vector& ids, uint16_t deltaN, const std::vector>& samples); size_t getNumInstruments() const override; std::string getInstrumentIdentifier(size_t index) const override; std::string getInstrumentName(size_t) const override; AbstractInstrument* loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const override; private: std::vector ids_; uint16_t deltaN_; std::vector> samples_; }; class PziBank final : public AbstractBank { public: PziBank(const std::vector& ids, const std::vector& deltaNs, const std::vector& isRepeatedList, const std::vector>& samples); size_t getNumInstruments() const override; std::string getInstrumentIdentifier(size_t index) const override; std::string getInstrumentName(size_t) const override; AbstractInstrument* loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const override; private: std::vector ids_; std::vector deltaNs_; std::vector isRepeatedList_; std::vector> samples_; }; class Mucom88Bank final : public AbstractBank { public: Mucom88Bank(const std::vector& ids, const std::vector& names, const std::vector& ctrs); size_t getNumInstruments() const override; std::string getInstrumentIdentifier(size_t index) const override; std::string getInstrumentName(size_t index) const override; AbstractInstrument* loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const override; void setInstrumentName(size_t index, const std::string& name); private: std::vector ids_; std::vector names_; std::vector instCtrs_; }; class PmbBank final : public AbstractBank { public: PmbBank(const std::vector& ids, const std::vector& names, const std::vector>& samples); size_t getNumInstruments() const override; std::string getInstrumentIdentifier(size_t index) const override; std::string getInstrumentName(size_t) const override; AbstractInstrument* loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const override; void setInstrumentName(size_t index, const std::string& name); private: std::vector ids_; std::vector names_; std::vector> samples_; }; BambooTracker-0.6.5/BambooTracker/instrument/effect_iterator.cpp000066400000000000000000000115701476276175200250560ustar00rootroot00000000000000/* * Copyright (C) 2018-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "effect_iterator.hpp" #include "note.hpp" #include #include "utils.hpp" namespace { const InstrumentSequenceBaseUnit ARP_CENTER(Note::DEFAULT_NOTE_NUM); } ArpeggioEffectIterator::ArpeggioEffectIterator(int second, int third) : SequenceIteratorInterface(2), second_(second + ARP_CENTER.data), third_(third + ARP_CENTER.data) { } InstrumentSequenceBaseUnit ArpeggioEffectIterator::data() const noexcept { switch (pos_) { case 0: return ARP_CENTER; case 1: return second_; case 2: return third_; default: return InstrumentSequenceBaseUnit(); } } int ArpeggioEffectIterator::next() { state_ = SequenceIteratorState::Run; pos_ = (pos_ + 1) % 3; return pos_; } int ArpeggioEffectIterator::front() { state_ = SequenceIteratorState::Run; pos_ = 0; return 0; } int ArpeggioEffectIterator::end() { state_ = SequenceIteratorState::End; pos_ = END_SEQ_POS; return END_SEQ_POS; } WavingEffectIterator::WavingEffectIterator(int period, int depth) { for (int i = 0; i <= period; ++i) { seq_.emplace_back(i * depth); } for (size_t i = static_cast(period - 1); i > 0; --i) { seq_.push_back(seq_.at(i)); } size_t p2 = static_cast(period) << 1; for (size_t i = 0; i < p2; ++i) { seq_.emplace_back(-seq_.at(i).data); } pos_ = static_cast(seq_.size()) - 1; } InstrumentSequenceBaseUnit WavingEffectIterator::data() const { return (hasEnded() ? InstrumentSequenceBaseUnit() : seq_.at(static_cast(pos_))); } int WavingEffectIterator::next() { state_ = SequenceIteratorState::Run; pos_ = (pos_ + 1) % static_cast(seq_.size()); return pos_; } int WavingEffectIterator::front() { state_ = SequenceIteratorState::Run; pos_ = 0; return 0; } int WavingEffectIterator::end() { state_ = SequenceIteratorState::End; pos_ = END_SEQ_POS; return END_SEQ_POS; } NoteSlideEffectIterator::NoteSlideEffectIterator(int speed, int semitone) : SequenceIteratorInterface(0) { int d = semitone * Note::SEMITONE_PITCH; if (speed) { int prev = 0; for (int i = 0; i <= speed; ++i) { int dif = d * i / speed - prev; seq_.emplace_back(dif); prev += dif; } } else { seq_.emplace_back(d); } } InstrumentSequenceBaseUnit NoteSlideEffectIterator::data() const { return (hasEnded() ? InstrumentSequenceBaseUnit() : seq_.at(static_cast(pos_))); } int NoteSlideEffectIterator::next() { if (!hasEnded()) { if (state_ == SequenceIteratorState::NotBegin || ++pos_ < static_cast(seq_.size())) { state_ = SequenceIteratorState::Run; } else { state_ = SequenceIteratorState::End; pos_ = END_SEQ_POS; } } return pos_; } int NoteSlideEffectIterator::front() { state_ = SequenceIteratorState::Run; pos_ = 0; return 0; } int NoteSlideEffectIterator::end() { state_ = SequenceIteratorState::End; pos_ = END_SEQ_POS; return END_SEQ_POS; } XVolumeSlideEffectIterator::XVolumeSlideEffectIterator(int factor, int cycleCount) : SequenceIteratorInterface(END_SEQ_POS), mem_(0), sum_(0), factor_(factor), cycleCount_(cycleCount) { } InstrumentSequenceBaseUnit XVolumeSlideEffectIterator::data() const noexcept { return InstrumentSequenceBaseUnit(mem_); } int XVolumeSlideEffectIterator::next() { if (state_ != SequenceIteratorState::Run) { state_ = SequenceIteratorState::Run; pos_ = 0; mem_ = 0; sum_ = 0; } else { int prevSum = std::exchange(sum_, ++pos_ * factor_ / cycleCount_); mem_ = sum_ - prevSum; pos_ %= cycleCount_; sum_ %= factor_; } return pos_; } int XVolumeSlideEffectIterator::front() { state_ = SequenceIteratorState::Run; pos_ = 0; mem_ = 0; sum_ = 0; return 0; } int XVolumeSlideEffectIterator::end() { state_ = SequenceIteratorState::End; pos_ = END_SEQ_POS; mem_ = 0; sum_ = 0; return pos_; } BambooTracker-0.6.5/BambooTracker/instrument/effect_iterator.hpp000066400000000000000000000061571476276175200250700ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include "sequence_iterator_interface.hpp" #include "sequence_property.hpp" class ArpeggioEffectIterator : public SequenceIteratorInterface { public: ArpeggioEffectIterator(int second, int third); SequenceType type() const noexcept override { return SequenceType::AbsoluteSequence; } InstrumentSequenceBaseUnit data() const noexcept override; int next() override; int front() override; inline int release() override { return next(); } int end() override; private: InstrumentSequenceBaseUnit second_, third_; }; class WavingEffectIterator : public SequenceIteratorInterface { public: WavingEffectIterator(int period, int depth); SequenceType type() const noexcept override { return SequenceType::AbsoluteSequence; } InstrumentSequenceBaseUnit data() const override; int next() override; int front() override; inline int release() override { return next(); } int end() override; private: std::vector seq_; }; class NoteSlideEffectIterator : public SequenceIteratorInterface { public: NoteSlideEffectIterator(int speed, int semitone); SequenceType type() const noexcept override { return SequenceType::AbsoluteSequence; } InstrumentSequenceBaseUnit data() const override; int next() override; int front() override; inline int release() override { return next(); } int end() override; private: std::vector seq_; }; class XVolumeSlideEffectIterator : public SequenceIteratorInterface { public: XVolumeSlideEffectIterator(int factor, int cycleCount); SequenceType type() const noexcept override { return SequenceType::AbsoluteSequence; } InstrumentSequenceBaseUnit data() const noexcept override; int next() override; // Use next() instead of front() and release() int front() override; inline int release() override { return next(); } int end() override; private: int mem_, sum_; int factor_; int cycleCount_; }; BambooTracker-0.6.5/BambooTracker/instrument/envelope_fm.cpp000066400000000000000000000066771476276175200242240ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "envelope_fm.hpp" namespace { const std::unordered_map DEF_PARAMS = { { FMEnvelopeParameter::AL, 4 }, { FMEnvelopeParameter::FB, 0 }, { FMEnvelopeParameter::AR1, 31 }, { FMEnvelopeParameter::DR1, 0 }, { FMEnvelopeParameter::SR1, 0 }, { FMEnvelopeParameter::RR1, 7 }, { FMEnvelopeParameter::SL1, 0 }, { FMEnvelopeParameter::TL1, 32 }, { FMEnvelopeParameter::KS1, 0 }, { FMEnvelopeParameter::ML1, 0 }, { FMEnvelopeParameter::DT1, 0 }, { FMEnvelopeParameter::SSGEG1, -1 }, { FMEnvelopeParameter::AR2, 31 }, { FMEnvelopeParameter::DR2, 0 }, { FMEnvelopeParameter::SR2, 0 }, { FMEnvelopeParameter::RR2, 7 }, { FMEnvelopeParameter::SL2, 0 }, { FMEnvelopeParameter::TL2, 0 }, { FMEnvelopeParameter::KS2, 0 }, { FMEnvelopeParameter::ML2, 0 }, { FMEnvelopeParameter::DT2, 0 }, { FMEnvelopeParameter::SSGEG2, -1 }, { FMEnvelopeParameter::AR3, 31 }, { FMEnvelopeParameter::DR3, 0 }, { FMEnvelopeParameter::SR3, 0 }, { FMEnvelopeParameter::RR3, 7 }, { FMEnvelopeParameter::SL3, 0 }, { FMEnvelopeParameter::TL3, 32 }, { FMEnvelopeParameter::KS3, 0 }, { FMEnvelopeParameter::ML3, 0 }, { FMEnvelopeParameter::DT3, 0 }, { FMEnvelopeParameter::SSGEG3, -1 }, { FMEnvelopeParameter::AR4, 31 }, { FMEnvelopeParameter::DR4, 0 }, { FMEnvelopeParameter::SR4, 0 }, { FMEnvelopeParameter::RR4, 7 }, { FMEnvelopeParameter::SL4, 0 }, { FMEnvelopeParameter::TL4, 0 }, { FMEnvelopeParameter::KS4, 0 }, { FMEnvelopeParameter::ML4, 0 }, { FMEnvelopeParameter::DT4, 0 }, { FMEnvelopeParameter::SSGEG4, -1 } }; } EnvelopeFM::EnvelopeFM(int num) : AbstractInstrumentProperty(num) { clearParameters(); } std::unique_ptr EnvelopeFM::clone() { std::unique_ptr clone = std::make_unique(*this); clone->clearUserInstruments(); return clone; } bool EnvelopeFM::getOperatorEnabled(int num) const { return isEnabledOp_.test(num); } void EnvelopeFM::setOperatorEnabled(int num, bool enabled) { isEnabledOp_.set(num, enabled); } int EnvelopeFM::getParameterValue(FMEnvelopeParameter param) const { return params_.at(param); } void EnvelopeFM::setParameterValue(FMEnvelopeParameter param, int value) { params_.at(param) = value; } bool EnvelopeFM::isEdited() const { return (params_ != DEF_PARAMS || !isEnabledOp_.all()); } void EnvelopeFM::clearParameters() { params_ = DEF_PARAMS; isEnabledOp_.set(); } BambooTracker-0.6.5/BambooTracker/instrument/envelope_fm.hpp000066400000000000000000000043521476276175200242150ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include "abstract_instrument_property.hpp" #include "enum_hash.hpp" enum class FMEnvelopeParameter { AL, FB, AR1, DR1, SR1, RR1, SL1, TL1, KS1, ML1, DT1, AR2, DR2, SR2, RR2, SL2, TL2, KS2, ML2, DT2, AR3, DR3, SR3, RR3, SL3, TL3, KS3, ML3, DT3, AR4, DR4, SR4, RR4, SL4, TL4, KS4, ML4, DT4, SSGEG1, SSGEG2, SSGEG3, SSGEG4 }; class EnvelopeFM final : public AbstractInstrumentProperty { public: explicit EnvelopeFM(int num); friend bool operator==(const EnvelopeFM& a, const EnvelopeFM& b) { return (a.params_ == b.params_ && a.isEnabledOp_ == b.isEnabledOp_); } friend bool operator!=(const EnvelopeFM& a, const EnvelopeFM& b) { return !(a == b); } std::unique_ptr clone(); bool getOperatorEnabled(int num) const; void setOperatorEnabled(int num, bool enabled); int getParameterValue(FMEnvelopeParameter param) const; void setParameterValue(FMEnvelopeParameter param, int value); bool isEdited() const override; void clearParameters() override; private: std::unordered_map params_; std::bitset<4> isEnabledOp_; }; BambooTracker-0.6.5/BambooTracker/instrument/instrument.cpp000066400000000000000000000454111476276175200241220ustar00rootroot00000000000000/* * Copyright (C) 2018-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "instrument.hpp" #include "instruments_manager.hpp" #include "utils.hpp" AbstractInstrument::AbstractInstrument(int number, SoundSource src, InstrumentType type, const std::string& name, InstrumentsManager* owner) : owner_(owner), number_(number), name_(name), sndSrc_(src), instType_(type) { } bool AbstractInstrument::isRegisteredWithManager() const { return (this == owner_->getInstrumentSharedPtr(number_).get()); } /****************************************/ InstrumentFM::InstrumentFM(int number, const std::string& name, InstrumentsManager* owner) : AbstractInstrument(number, SoundSource::FM, InstrumentType::FM, name, owner), envNum_(0), lfoEnabled_(false), lfoNum_(0), panEnabled_(false), panNum_(0) { opSeqEnabled_ = { { FMEnvelopeParameter::AL, false }, { FMEnvelopeParameter::FB, false }, { FMEnvelopeParameter::AR1, false }, { FMEnvelopeParameter::DR1, false }, { FMEnvelopeParameter::SR1, false }, { FMEnvelopeParameter::RR1, false }, { FMEnvelopeParameter::SL1, false }, { FMEnvelopeParameter::TL1, false }, { FMEnvelopeParameter::KS1, false }, { FMEnvelopeParameter::ML1, false }, { FMEnvelopeParameter::DT1, false }, { FMEnvelopeParameter::AR2, false }, { FMEnvelopeParameter::DR2, false }, { FMEnvelopeParameter::SR2, false }, { FMEnvelopeParameter::RR2, false }, { FMEnvelopeParameter::SL2, false }, { FMEnvelopeParameter::TL2, false }, { FMEnvelopeParameter::KS2, false }, { FMEnvelopeParameter::ML2, false }, { FMEnvelopeParameter::DT2, false }, { FMEnvelopeParameter::AR3, false }, { FMEnvelopeParameter::DR3, false }, { FMEnvelopeParameter::SR3, false }, { FMEnvelopeParameter::RR3, false }, { FMEnvelopeParameter::SL3, false }, { FMEnvelopeParameter::TL3, false }, { FMEnvelopeParameter::KS3, false }, { FMEnvelopeParameter::ML3, false }, { FMEnvelopeParameter::DT3, false }, { FMEnvelopeParameter::AR4, false }, { FMEnvelopeParameter::DR4, false }, { FMEnvelopeParameter::SR4, false }, { FMEnvelopeParameter::RR4, false }, { FMEnvelopeParameter::SL4, false }, { FMEnvelopeParameter::TL4, false }, { FMEnvelopeParameter::KS4, false }, { FMEnvelopeParameter::ML4, false }, { FMEnvelopeParameter::DT4, false } }; opSeqNum_ = { { FMEnvelopeParameter::AL, 0 }, { FMEnvelopeParameter::FB, 0 }, { FMEnvelopeParameter::AR1, 0 }, { FMEnvelopeParameter::DR1, 0 }, { FMEnvelopeParameter::SR1, 0 }, { FMEnvelopeParameter::RR1, 0 }, { FMEnvelopeParameter::SL1, 0 }, { FMEnvelopeParameter::TL1, 0 }, { FMEnvelopeParameter::KS1, 0 }, { FMEnvelopeParameter::ML1, 0 }, { FMEnvelopeParameter::DT1, 0 }, { FMEnvelopeParameter::AR2, 0 }, { FMEnvelopeParameter::DR2, 0 }, { FMEnvelopeParameter::SR2, 0 }, { FMEnvelopeParameter::RR2, 0 }, { FMEnvelopeParameter::SL2, 0 }, { FMEnvelopeParameter::TL2, 0 }, { FMEnvelopeParameter::KS2, 0 }, { FMEnvelopeParameter::ML2, 0 }, { FMEnvelopeParameter::DT2, 0 }, { FMEnvelopeParameter::AR3, 0 }, { FMEnvelopeParameter::DR3, 0 }, { FMEnvelopeParameter::SR3, 0 }, { FMEnvelopeParameter::RR3, 0 }, { FMEnvelopeParameter::SL3, 0 }, { FMEnvelopeParameter::TL3, 0 }, { FMEnvelopeParameter::KS3, 0 }, { FMEnvelopeParameter::ML3, 0 }, { FMEnvelopeParameter::DT3, 0 }, { FMEnvelopeParameter::AR4, 0 }, { FMEnvelopeParameter::DR4, 0 }, { FMEnvelopeParameter::SR4, 0 }, { FMEnvelopeParameter::RR4, 0 }, { FMEnvelopeParameter::SL4, 0 }, { FMEnvelopeParameter::TL4, 0 }, { FMEnvelopeParameter::KS4, 0 }, { FMEnvelopeParameter::ML4, 0 }, { FMEnvelopeParameter::DT4, 0 } }; arpEnabled_ = { { FMOperatorType::All, false }, { FMOperatorType::Op1, false }, { FMOperatorType::Op2, false }, { FMOperatorType::Op3, false }, { FMOperatorType::Op4, false } }; arpNum_ = { { FMOperatorType::All, 0 }, { FMOperatorType::Op1, 0 }, { FMOperatorType::Op2, 0 }, { FMOperatorType::Op3, 0 }, { FMOperatorType::Op4, 0 } }; ptEnabled_ = { { FMOperatorType::All, false }, { FMOperatorType::Op1, false }, { FMOperatorType::Op2, false }, { FMOperatorType::Op3, false }, { FMOperatorType::Op4, false } }; ptNum_ = { { FMOperatorType::All, 0 }, { FMOperatorType::Op1, 0 }, { FMOperatorType::Op2, 0 }, { FMOperatorType::Op3, 0 }, { FMOperatorType::Op4, 0 } }; envResetEnabled_ = { { FMOperatorType::All, false }, { FMOperatorType::Op1, false }, { FMOperatorType::Op2, false }, { FMOperatorType::Op3, false }, { FMOperatorType::Op4, false } }; } AbstractInstrument* InstrumentFM::clone() { return new InstrumentFM(*this); } int InstrumentFM::getEnvelopeParameter(FMEnvelopeParameter param) const { return owner_->getEnvelopeFMParameter(envNum_, param); } bool InstrumentFM::getOperatorEnabled(int n) const { return owner_->getEnvelopeFMOperatorEnabled(envNum_, n); } int InstrumentFM::getLFOParameter(FMLFOParameter param) const { return owner_->getLFOFMparameter(lfoNum_, param); } void InstrumentFM::setOperatorSequenceEnabled(FMEnvelopeParameter param, bool enabled) { opSeqEnabled_.at(param) = enabled; } bool InstrumentFM::getOperatorSequenceEnabled(FMEnvelopeParameter param) const { return opSeqEnabled_.at(param); } void InstrumentFM::setOperatorSequenceNumber(FMEnvelopeParameter param, int n) { opSeqNum_.at(param) = n; } int InstrumentFM::getOperatorSequenceNumber(FMEnvelopeParameter param) const { return opSeqNum_.at(param); } std::vector InstrumentFM::getOperatorSequenceSequence(FMEnvelopeParameter param) const { return owner_->getOperatorSequenceFMSequence(param, opSeqNum_.at(param)); } InstrumentSequenceLoopRoot InstrumentFM::getOperatorSequenceLoopRoot(FMEnvelopeParameter param) const { return owner_->getOperatorSequenceFMLoopRoot(param, opSeqNum_.at(param)); } InstrumentSequenceRelease InstrumentFM::getOperatorSequenceRelease(FMEnvelopeParameter param) const { return owner_->getOperatorSequenceFMRelease(param, opSeqNum_.at(param)); } FMOperatorSequenceIter InstrumentFM::getOperatorSequenceSequenceIterator(FMEnvelopeParameter param) const { return owner_->getOperatorSequenceFMIterator(param, opSeqNum_.at(param)); } void InstrumentFM::setArpeggioEnabled(FMOperatorType op, bool enabled) { arpEnabled_.at(op) = enabled; } bool InstrumentFM::getArpeggioEnabled(FMOperatorType op) const { return arpEnabled_.at(op); } void InstrumentFM::setArpeggioNumber(FMOperatorType op, int n) { arpNum_.at(op) = n; } int InstrumentFM::getArpeggioNumber(FMOperatorType op) const { return arpNum_.at(op); } SequenceType InstrumentFM::getArpeggioType(FMOperatorType op) const { return owner_->getArpeggioFMType(arpNum_.at(op)); } std::vector InstrumentFM::getArpeggioSequence(FMOperatorType op) const { return owner_->getArpeggioFMSequence(arpNum_.at(op)); } InstrumentSequenceLoopRoot InstrumentFM::getArpeggioLoopRoot(FMOperatorType op) const { return owner_->getArpeggioFMLoopRoot(arpNum_.at(op)); } InstrumentSequenceRelease InstrumentFM::getArpeggioRelease(FMOperatorType op) const { return owner_->getArpeggioFMRelease(arpNum_.at(op)); } ArpeggioIter InstrumentFM::getArpeggioSequenceIterator(FMOperatorType op) const { return owner_->getArpeggioFMIterator(arpNum_.at(op)); } void InstrumentFM::setPitchEnabled(FMOperatorType op, bool enabled) { ptEnabled_.at(op) = enabled; } bool InstrumentFM::getPitchEnabled(FMOperatorType op) const { return ptEnabled_.at(op); } void InstrumentFM::setPitchNumber(FMOperatorType op, int n) { ptNum_.at(op) = n; } int InstrumentFM::getPitchNumber(FMOperatorType op) const { return ptNum_.at(op); } SequenceType InstrumentFM::getPitchType(FMOperatorType op) const { return owner_->getPitchFMType(ptNum_.at(op)); } std::vector InstrumentFM::getPitchSequence(FMOperatorType op) const { return owner_->getPitchFMSequence(ptNum_.at(op)); } InstrumentSequenceLoopRoot InstrumentFM::getPitchLoopRoot(FMOperatorType op) const { return owner_->getPitchFMLoopRoot(ptNum_.at(op)); } InstrumentSequenceRelease InstrumentFM::getPitchRelease(FMOperatorType op) const { return owner_->getPitchFMRelease(ptNum_.at(op)); } PitchIter InstrumentFM::getPitchSequenceIterator(FMOperatorType op) const { return owner_->getPitchFMIterator(ptNum_.at(op)); } void InstrumentFM::setPanEnabled(bool enabled) { panEnabled_ = enabled; } bool InstrumentFM::getPanEnabled() const { return panEnabled_; } void InstrumentFM::setPanNumber(int n) { panNum_ = n; } int InstrumentFM::getPanNumber() const { return panNum_; } std::vector InstrumentFM::getPanSequence() const { return owner_->getPanFMSequence(panNum_); } InstrumentSequenceLoopRoot InstrumentFM::getPanLoopRoot() const { return owner_->getPanFMLoopRoot(panNum_); } InstrumentSequenceRelease InstrumentFM::getPanRelease() const { return owner_->getPanFMRelease(panNum_); } PanIter InstrumentFM::getPanSequenceIterator() const { return owner_->getPanFMIterator(panNum_); } void InstrumentFM::setEnvelopeResetEnabled(FMOperatorType op, bool enabled) { envResetEnabled_.at(op) = enabled; } bool InstrumentFM::getEnvelopeResetEnabled(FMOperatorType op) const { return envResetEnabled_.at(op); } /****************************************/ InstrumentSSG::InstrumentSSG(int number, const std::string& name, InstrumentsManager* owner) : AbstractInstrument(number, SoundSource::SSG, InstrumentType::SSG, name, owner), wfEnabled_(false), wfNum_(0), tnEnabled_(false), tnNum_(0), envEnabled_(false), envNum_(0), arpEnabled_(false), arpNum_(0), ptEnabled_(false), ptNum_(0) { } AbstractInstrument* InstrumentSSG::clone() { return new InstrumentSSG(*this); } std::vector InstrumentSSG::getWaveformSequence() const { return owner_->getWaveformSSGSequence(wfNum_); } InstrumentSequenceLoopRoot InstrumentSSG::getWaveformLoopRoot() const { return owner_->getWaveformSSGLoopRoot(wfNum_); } InstrumentSequenceRelease InstrumentSSG::getWaveformRelease() const { return owner_->getWaveformSSGRelease(wfNum_); } SSGWaveformIter InstrumentSSG::getWaveformSequenceIterator() const { return owner_->getWaveformSSGIterator(wfNum_); } std::vector InstrumentSSG::getToneNoiseSequence() const { return owner_->getToneNoiseSSGSequence(tnNum_); } InstrumentSequenceLoopRoot InstrumentSSG::getToneNoiseLoopRoot() const { return owner_->getToneNoiseSSGLoopRoot(tnNum_); } InstrumentSequenceRelease InstrumentSSG::getToneNoiseRelease() const { return owner_->getToneNoiseSSGRelease(tnNum_); } SSGToneNoiseIter InstrumentSSG::getToneNoiseSequenceIterator() const { return owner_->getToneNoiseSSGIterator(tnNum_); } std::vector InstrumentSSG::getEnvelopeSequence() const { return owner_->getEnvelopeSSGSequence(envNum_); } InstrumentSequenceLoopRoot InstrumentSSG::getEnvelopeLoopRoot() const { return owner_->getEnvelopeSSGLoopRoot(envNum_); } InstrumentSequenceRelease InstrumentSSG::getEnvelopeRelease() const { return owner_->getEnvelopeSSGRelease(envNum_); } SSGEnvelopeIter InstrumentSSG::getEnvelopeSequenceIterator() const { return owner_->getEnvelopeSSGIterator(envNum_); } SequenceType InstrumentSSG::getArpeggioType() const { return owner_->getArpeggioSSGType(arpNum_); } std::vector InstrumentSSG::getArpeggioSequence() const { return owner_->getArpeggioSSGSequence(arpNum_); } InstrumentSequenceLoopRoot InstrumentSSG::getArpeggioLoopRoot() const { return owner_->getArpeggioSSGLoopRoot(arpNum_); } InstrumentSequenceRelease InstrumentSSG::getArpeggioRelease() const { return owner_->getArpeggioSSGRelease(arpNum_); } ArpeggioIter InstrumentSSG::getArpeggioSequenceIterator() const { return owner_->getArpeggioSSGIterator(arpNum_); } SequenceType InstrumentSSG::getPitchType() const { return owner_->getPitchSSGType(ptNum_); } std::vector InstrumentSSG::getPitchSequence() const { return owner_->getPitchSSGSequence(ptNum_); } InstrumentSequenceLoopRoot InstrumentSSG::getPitchLoopRoot() const { return owner_->getPitchSSGLoopRoot(ptNum_); } InstrumentSequenceRelease InstrumentSSG::getPitchRelease() const { return owner_->getPitchSSGRelease(ptNum_); } PitchIter InstrumentSSG::getPitchSequenceIterator() const { return owner_->getPitchSSGIterator(ptNum_); } /****************************************/ InstrumentADPCM::InstrumentADPCM(int number, const std::string& name, InstrumentsManager* owner) : AbstractInstrument(number, SoundSource::ADPCM, InstrumentType::ADPCM, name, owner), sampNum_(0), envEnabled_(false), envNum_(0), arpEnabled_(false), arpNum_(0), ptEnabled_(false), ptNum_(0), panEnabled_(false), panNum_(0) { } AbstractInstrument* InstrumentADPCM::clone() { return new InstrumentADPCM(*this); } int InstrumentADPCM::getSampleRootKeyNumber() const { return owner_->getSampleADPCMRootKeyNumber(sampNum_); } int InstrumentADPCM::getSampleRootDeltaN() const { return owner_->getSampleADPCMRootDeltaN(sampNum_); } bool InstrumentADPCM::isSampleRepeatable() const { return owner_->isSampleADPCMRepeatable(sampNum_); } SampleRepeatFlag InstrumentADPCM::getSampleRepeatFlag() const { return owner_->getSampleADPCMRepeatFlag(sampNum_); } SampleRepeatRange InstrumentADPCM::getSampleRepeatRange() const { return owner_->getSampleADPCMRepeatRange(sampNum_); } std::vector InstrumentADPCM::getRawSample() const { return owner_->getSampleADPCMRawSample(sampNum_); } size_t InstrumentADPCM::getSampleStartAddress() const { return owner_->getSampleADPCMStartAddress(sampNum_); } size_t InstrumentADPCM::getSampleStopAddress() const { return owner_->getSampleADPCMStopAddress(sampNum_); } std::vector InstrumentADPCM::getEnvelopeSequence() const { return owner_->getEnvelopeADPCMSequence(envNum_); } InstrumentSequenceLoopRoot InstrumentADPCM::getEnvelopeLoopRoot() const { return owner_->getEnvelopeADPCMLoopRoot(envNum_); } InstrumentSequenceRelease InstrumentADPCM::getEnvelopeRelease() const { return owner_->getEnvelopeADPCMRelease(envNum_); } ADPCMEnvelopeIter InstrumentADPCM::getEnvelopeSequenceIterator() const { return owner_->getEnvelopeADPCMIterator(envNum_); } SequenceType InstrumentADPCM::getArpeggioType() const { return owner_->getArpeggioADPCMType(arpNum_); } std::vector InstrumentADPCM::getArpeggioSequence() const { return owner_->getArpeggioADPCMSequence(arpNum_); } InstrumentSequenceLoopRoot InstrumentADPCM::getArpeggioLoopRoot() const { return owner_->getArpeggioADPCMLoopRoot(arpNum_); } InstrumentSequenceRelease InstrumentADPCM::getArpeggioRelease() const { return owner_->getArpeggioADPCMRelease(arpNum_); } ArpeggioIter InstrumentADPCM::getArpeggioSequenceIterator() const { return owner_->getArpeggioADPCMIterator(arpNum_); } SequenceType InstrumentADPCM::getPitchType() const { return owner_->getPitchADPCMType(ptNum_); } std::vector InstrumentADPCM::getPitchSequence() const { return owner_->getPitchADPCMSequence(ptNum_); } InstrumentSequenceLoopRoot InstrumentADPCM::getPitchLoopRoot() const { return owner_->getPitchADPCMLoopRoot(ptNum_); } InstrumentSequenceRelease InstrumentADPCM::getPitchRelease() const { return owner_->getPitchADPCMRelease(ptNum_); } PitchIter InstrumentADPCM::getPitchSequenceIterator() const { return owner_->getPitchADPCMIterator(ptNum_); } std::vector InstrumentADPCM::getPanSequence() const { return owner_->getPanADPCMSequence(panNum_); } InstrumentSequenceLoopRoot InstrumentADPCM::getPanLoopRoot() const { return owner_->getPanADPCMLoopRoot(panNum_); } InstrumentSequenceRelease InstrumentADPCM::getPanRelease() const { return owner_->getPanADPCMRelease(panNum_); } PanIter InstrumentADPCM::getPanSequenceIterator() const { return owner_->getPanADPCMIterator(panNum_); } /****************************************/ InstrumentDrumkit::InstrumentDrumkit(int number, const std::string& name, InstrumentsManager* owner) : AbstractInstrument(number, SoundSource::ADPCM, InstrumentType::Drumkit, name, owner) { } AbstractInstrument* InstrumentDrumkit::clone() { return new InstrumentDrumkit(*this); } std::vector InstrumentDrumkit::getAssignedKeys() const { return utils::getMapKeys(kit_); } void InstrumentDrumkit::setSampleEnabled(int key, bool enabled) { if (enabled) kit_[key] = { 0, 0, PanType::CENTER }; else kit_.erase(key); } bool InstrumentDrumkit::getSampleEnabled(int key) const { return kit_.count(key); } void InstrumentDrumkit::setSampleNumber(int key, int n) { if (kit_.count(key)) kit_.at(key).sampNum = n; } int InstrumentDrumkit::getSampleNumber(int key) const { return kit_.at(key).sampNum; } int InstrumentDrumkit::getSampleRootKeyNumber(int key) const { return owner_->getSampleADPCMRootKeyNumber(kit_.at(key).sampNum); } int InstrumentDrumkit::getSampleRootDeltaN(int key) const { return owner_->getSampleADPCMRootDeltaN(kit_.at(key).sampNum); } bool InstrumentDrumkit::isSampleRepeatable(int key) const { return owner_->isSampleADPCMRepeatable(kit_.at(key).sampNum); } SampleRepeatFlag InstrumentDrumkit::getSampleRepeatFlag(int key) const { return owner_->getSampleADPCMRepeatFlag(kit_.at(key).sampNum); } SampleRepeatRange InstrumentDrumkit::getSampleRepeatRange(int key) const { return owner_->getSampleADPCMRepeatRange(kit_.at(key).sampNum); } std::vector InstrumentDrumkit::getRawSample(int key) const { return owner_->getSampleADPCMRawSample(kit_.at(key).sampNum); } size_t InstrumentDrumkit::getSampleStartAddress(int key) const { return owner_->getSampleADPCMStartAddress(kit_.at(key).sampNum); } size_t InstrumentDrumkit::getSampleStopAddress(int key) const { return owner_->getSampleADPCMStopAddress(kit_.at(key).sampNum); } void InstrumentDrumkit::setPitch(int key, int pitch) { if (kit_.count(key)) kit_.at(key).pitch = pitch; } int InstrumentDrumkit::getPitch(int key) const { return kit_.at(key).pitch; } void InstrumentDrumkit::setPan(int key, int pan) { if (kit_.count(key)) kit_.at(key).pan = pan; } int InstrumentDrumkit::getPan(int key) const { return kit_.at(key).pan; } BambooTracker-0.6.5/BambooTracker/instrument/instrument.hpp000066400000000000000000000303131476276175200241220ustar00rootroot00000000000000/* * Copyright (C) 2018-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include #include "envelope_fm.hpp" #include "lfo_fm.hpp" #include "sequence_property.hpp" #include "sample_repeat.hpp" #include "instrument_property_defs.hpp" #include "enum_hash.hpp" #include "bamboo_tracker_defs.hpp" class InstrumentsManager; enum class InstrumentType : int { FM, SSG, ADPCM, Drumkit }; class AbstractInstrument { public: virtual ~AbstractInstrument() = default; inline int getNumber() const noexcept { return number_; } inline void setNumber(int n) noexcept { number_ = n; } inline SoundSource getSoundSource() const noexcept { return sndSrc_; } inline InstrumentType getType() const noexcept { return instType_; } inline std::string getName() const noexcept { return name_; } inline void setName(const std::string& name) { name_ = name; } bool isRegisteredWithManager() const; virtual AbstractInstrument* clone() = 0; protected: InstrumentsManager* owner_; int number_; std::string name_; // UTF-8 AbstractInstrument(int number, SoundSource src, InstrumentType type, const std::string& name, InstrumentsManager* owner); private: const SoundSource sndSrc_; const InstrumentType instType_; }; class InstrumentFM final : public AbstractInstrument { public: InstrumentFM(int number, const std::string& name, InstrumentsManager* owner); AbstractInstrument* clone() override; inline void setEnvelopeNumber(int n) noexcept { envNum_ = n; } inline int getEnvelopeNumber() const noexcept { return envNum_; } int getEnvelopeParameter(FMEnvelopeParameter param) const; bool getOperatorEnabled(int n) const; inline void setLFOEnabled(bool enabled) noexcept { lfoEnabled_ = enabled; } inline bool getLFOEnabled() const noexcept { return lfoEnabled_; } inline void setLFONumber(int n) noexcept { lfoNum_ = n; } inline int getLFONumber() const noexcept { return lfoNum_; } int getLFOParameter(FMLFOParameter param) const; void setOperatorSequenceEnabled(FMEnvelopeParameter param, bool enabled); bool getOperatorSequenceEnabled(FMEnvelopeParameter param) const; void setOperatorSequenceNumber(FMEnvelopeParameter param, int n); int getOperatorSequenceNumber(FMEnvelopeParameter param) const; std::vector getOperatorSequenceSequence(FMEnvelopeParameter param) const; InstrumentSequenceLoopRoot getOperatorSequenceLoopRoot(FMEnvelopeParameter param) const; InstrumentSequenceRelease getOperatorSequenceRelease(FMEnvelopeParameter param) const; FMOperatorSequenceIter getOperatorSequenceSequenceIterator(FMEnvelopeParameter param) const; void setArpeggioEnabled(FMOperatorType op, bool enabled); bool getArpeggioEnabled(FMOperatorType op) const; void setArpeggioNumber(FMOperatorType op, int n); int getArpeggioNumber(FMOperatorType op) const; SequenceType getArpeggioType(FMOperatorType op) const; std::vector getArpeggioSequence(FMOperatorType op) const; InstrumentSequenceLoopRoot getArpeggioLoopRoot(FMOperatorType op) const; InstrumentSequenceRelease getArpeggioRelease(FMOperatorType op) const; ArpeggioIter getArpeggioSequenceIterator(FMOperatorType op) const; void setPitchEnabled(FMOperatorType op, bool enabled); bool getPitchEnabled(FMOperatorType op) const; void setPitchNumber(FMOperatorType op, int n); int getPitchNumber(FMOperatorType op) const; SequenceType getPitchType(FMOperatorType op) const; std::vector getPitchSequence(FMOperatorType op) const; InstrumentSequenceLoopRoot getPitchLoopRoot(FMOperatorType op) const; InstrumentSequenceRelease getPitchRelease(FMOperatorType op) const; PitchIter getPitchSequenceIterator(FMOperatorType op) const; void setPanEnabled(bool enabled); bool getPanEnabled() const; void setPanNumber(int n); int getPanNumber() const; std::vector getPanSequence() const; InstrumentSequenceLoopRoot getPanLoopRoot() const; InstrumentSequenceRelease getPanRelease() const; PanIter getPanSequenceIterator() const; void setEnvelopeResetEnabled(FMOperatorType op, bool enabled); bool getEnvelopeResetEnabled(FMOperatorType op) const; private: int envNum_; bool lfoEnabled_; int lfoNum_; std::unordered_map opSeqEnabled_; std::unordered_map opSeqNum_; std::unordered_map arpEnabled_; std::unordered_map arpNum_; std::unordered_map ptEnabled_; std::unordered_map ptNum_; bool panEnabled_; int panNum_; std::unordered_map envResetEnabled_; }; class InstrumentSSG final : public AbstractInstrument { public: InstrumentSSG(int number, const std::string& name, InstrumentsManager* owner); AbstractInstrument* clone() override; inline void setWaveformEnabled(bool enabled) noexcept { wfEnabled_ = enabled; } inline bool getWaveformEnabled() const noexcept { return wfEnabled_; } inline void setWaveformNumber(int n) noexcept { wfNum_ = n; } inline int getWaveformNumber() const noexcept { return wfNum_; } std::vector getWaveformSequence() const; InstrumentSequenceLoopRoot getWaveformLoopRoot() const; InstrumentSequenceRelease getWaveformRelease() const; SSGWaveformIter getWaveformSequenceIterator() const; inline void setToneNoiseEnabled(bool enabled) noexcept { tnEnabled_ = enabled; } inline bool getToneNoiseEnabled() const noexcept { return tnEnabled_; } inline void setToneNoiseNumber(int n) noexcept { tnNum_ = n; } inline int getToneNoiseNumber() const noexcept { return tnNum_; } std::vector getToneNoiseSequence() const; InstrumentSequenceLoopRoot getToneNoiseLoopRoot() const; InstrumentSequenceRelease getToneNoiseRelease() const; SSGToneNoiseIter getToneNoiseSequenceIterator() const; inline void setEnvelopeEnabled(bool enabled) noexcept { envEnabled_ = enabled; } inline bool getEnvelopeEnabled() const noexcept { return envEnabled_; } inline void setEnvelopeNumber(int n) noexcept { envNum_ = n; } inline int getEnvelopeNumber() const noexcept { return envNum_; } std::vector getEnvelopeSequence() const; InstrumentSequenceLoopRoot getEnvelopeLoopRoot() const; InstrumentSequenceRelease getEnvelopeRelease() const; SSGEnvelopeIter getEnvelopeSequenceIterator() const; inline void setArpeggioEnabled(bool enabled) noexcept { arpEnabled_ = enabled; } inline bool getArpeggioEnabled() const noexcept { return arpEnabled_; } inline void setArpeggioNumber(int n) noexcept { arpNum_ = n; } inline int getArpeggioNumber() const noexcept { return arpNum_; } SequenceType getArpeggioType() const; std::vector getArpeggioSequence() const; InstrumentSequenceLoopRoot getArpeggioLoopRoot() const; InstrumentSequenceRelease getArpeggioRelease() const; ArpeggioIter getArpeggioSequenceIterator() const; inline void setPitchEnabled(bool enabled) noexcept { ptEnabled_ = enabled; } inline bool getPitchEnabled() const noexcept { return ptEnabled_; } inline void setPitchNumber(int n) noexcept { ptNum_ = n; } inline int getPitchNumber() const noexcept { return ptNum_; } SequenceType getPitchType() const; std::vector getPitchSequence() const; InstrumentSequenceLoopRoot getPitchLoopRoot() const; InstrumentSequenceRelease getPitchRelease() const; PitchIter getPitchSequenceIterator() const; private: bool wfEnabled_; int wfNum_; bool tnEnabled_; int tnNum_; bool envEnabled_; int envNum_; bool arpEnabled_; int arpNum_; bool ptEnabled_; int ptNum_; }; class InstrumentADPCM final : public AbstractInstrument { public: InstrumentADPCM(int number, const std::string& name, InstrumentsManager* owner); AbstractInstrument* clone() override; inline void setSampleNumber(int n) noexcept { sampNum_ = n; } inline int getSampleNumber() const noexcept { return sampNum_; } int getSampleRootKeyNumber() const; int getSampleRootDeltaN() const; bool isSampleRepeatable() const; SampleRepeatFlag getSampleRepeatFlag() const; SampleRepeatRange getSampleRepeatRange() const; std::vector getRawSample() const; size_t getSampleStartAddress() const; size_t getSampleStopAddress() const; inline void setEnvelopeEnabled(bool enabled) noexcept { envEnabled_ = enabled; } inline bool getEnvelopeEnabled() const noexcept { return envEnabled_; } inline void setEnvelopeNumber(int n) noexcept { envNum_ = n; } inline int getEnvelopeNumber() const noexcept { return envNum_; } std::vector getEnvelopeSequence() const; InstrumentSequenceLoopRoot getEnvelopeLoopRoot() const; InstrumentSequenceRelease getEnvelopeRelease() const; ADPCMEnvelopeIter getEnvelopeSequenceIterator() const; inline void setArpeggioEnabled(bool enabled) noexcept { arpEnabled_ = enabled; } inline bool getArpeggioEnabled() const noexcept { return arpEnabled_; } inline void setArpeggioNumber(int n) noexcept { arpNum_ = n; } inline int getArpeggioNumber() const noexcept { return arpNum_; } SequenceType getArpeggioType() const; std::vector getArpeggioSequence() const; InstrumentSequenceLoopRoot getArpeggioLoopRoot() const; InstrumentSequenceRelease getArpeggioRelease() const; ArpeggioIter getArpeggioSequenceIterator() const; inline void setPitchEnabled(bool enabled) noexcept { ptEnabled_ = enabled; } inline bool getPitchEnabled() const noexcept { return ptEnabled_; } inline void setPitchNumber(int n) noexcept { ptNum_ = n; } inline int getPitchNumber() const noexcept { return ptNum_; } SequenceType getPitchType() const; std::vector getPitchSequence() const; InstrumentSequenceLoopRoot getPitchLoopRoot() const; InstrumentSequenceRelease getPitchRelease() const; PitchIter getPitchSequenceIterator() const; inline void setPanEnabled(bool enabled) noexcept { panEnabled_ = enabled; } inline bool getPanEnabled() const noexcept { return panEnabled_; } inline void setPanNumber(int n) noexcept { panNum_ = n; } inline int getPanNumber() const noexcept { return panNum_; } std::vector getPanSequence() const; InstrumentSequenceLoopRoot getPanLoopRoot() const; InstrumentSequenceRelease getPanRelease() const; PanIter getPanSequenceIterator() const; private: int sampNum_; bool envEnabled_; int envNum_; bool arpEnabled_; int arpNum_; bool ptEnabled_; int ptNum_; bool panEnabled_; int panNum_; }; class InstrumentDrumkit final : public AbstractInstrument { public: InstrumentDrumkit(int number, const std::string& name, InstrumentsManager* owner); AbstractInstrument* clone() override; std::vector getAssignedKeys() const; void setSampleEnabled(int key, bool enabled); bool getSampleEnabled(int key) const; void setSampleNumber(int key, int n); int getSampleNumber(int key) const; int getSampleRootKeyNumber(int key) const; int getSampleRootDeltaN(int key) const; bool isSampleRepeatable(int key) const; SampleRepeatFlag getSampleRepeatFlag(int key) const; SampleRepeatRange getSampleRepeatRange(int key) const; std::vector getRawSample(int key) const; size_t getSampleStartAddress(int key) const; size_t getSampleStopAddress(int key) const; void setPitch(int key, int pitch); int getPitch(int key) const; void setPan(int key, int pan); int getPan(int key) const; private: struct KitProperty { int sampNum, pitch, pan; }; std::unordered_map kit_; }; BambooTracker-0.6.5/BambooTracker/instrument/instrument_property_defs.hpp000066400000000000000000000057611476276175200271000ustar00rootroot00000000000000/* * Copyright (C) 2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "sequence_property.hpp" using FMOperatorSequenceUnit = InstrumentSequenceBaseUnit; using FMOperatorSequenceIter = std::unique_ptr::Iterator>; enum class FMOperatorType { All, Op1, Op2, Op3, Op4 }; using ArpeggioUnit = InstrumentSequenceBaseUnit; using ArpeggioIter = std::unique_ptr::Iterator>; using PitchUnit = InstrumentSequenceBaseUnit; using PitchIter = std::unique_ptr::Iterator>; constexpr int SEQ_PITCH_CENTER = 127; using PanUnit = InstrumentSequenceBaseUnit; using PanIter = std::unique_ptr::Iterator>; namespace PanType { enum : uint8_t { SILENCE = 0, RIGHT = 1, LEFT = 2, CENTER = RIGHT | LEFT }; } using SSGWaveformUnit = InstrumentSequenceExtendUnit; using SSGWaveformIter = std::unique_ptr::Iterator>; class SSGWaveformType { public: enum : int { UNSET = SSGWaveformUnit::ERR_DATA, SQUARE = 0, TRIANGLE = 1, SAW = 2, INVSAW = 3, SQM_TRIANGLE = 4, SQM_SAW = 5, SQM_INVSAW = 6 }; static bool testHardEnvelopeOccupancity(int form) { switch (form) { case SSGWaveformType::UNSET: case SSGWaveformType::SQUARE: return false; default: return true; } } SSGWaveformType() = delete; }; using SSGToneNoiseUnit = InstrumentSequenceBaseUnit; using SSGToneNoiseIter = std::unique_ptr::Iterator>; using SSGEnvelopeUnit = InstrumentSequenceExtendUnit; using SSGEnvelopeIter = std::unique_ptr::Iterator>; using ADPCMEnvelopeUnit = InstrumentSequenceBaseUnit; using ADPCMEnvelopeIter = std::unique_ptr::Iterator>; BambooTracker-0.6.5/BambooTracker/instrument/instruments_manager.cpp000066400000000000000000002706761476276175200260140ustar00rootroot00000000000000/* * Copyright (C) 2018-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "instruments_manager.hpp" #include #include #include #include #include #include #include "instrument.hpp" #include "note.hpp" #include "utils.hpp" namespace { // Constant const FMEnvelopeParameter ENV_FM_PARAMS[] = { FMEnvelopeParameter::AL, FMEnvelopeParameter::FB, FMEnvelopeParameter::AR1, FMEnvelopeParameter::DR1, FMEnvelopeParameter::SR1, FMEnvelopeParameter::RR1, FMEnvelopeParameter::SL1, FMEnvelopeParameter::TL1, FMEnvelopeParameter::KS1, FMEnvelopeParameter::ML1, FMEnvelopeParameter::DT1, FMEnvelopeParameter::AR2, FMEnvelopeParameter::DR2, FMEnvelopeParameter::SR2, FMEnvelopeParameter::RR2, FMEnvelopeParameter::SL2, FMEnvelopeParameter::TL2, FMEnvelopeParameter::KS2, FMEnvelopeParameter::ML2, FMEnvelopeParameter::DT2, FMEnvelopeParameter::AR3, FMEnvelopeParameter::DR3, FMEnvelopeParameter::SR3, FMEnvelopeParameter::RR3, FMEnvelopeParameter::SL3, FMEnvelopeParameter::TL3, FMEnvelopeParameter::KS3, FMEnvelopeParameter::ML3, FMEnvelopeParameter::DT3, FMEnvelopeParameter::AR4, FMEnvelopeParameter::DR4, FMEnvelopeParameter::SR4, FMEnvelopeParameter::RR4, FMEnvelopeParameter::SL4, FMEnvelopeParameter::TL4, FMEnvelopeParameter::KS4, FMEnvelopeParameter::ML4, FMEnvelopeParameter::DT4 }; const FMOperatorType FM_OP_TYPES[] = { FMOperatorType::All, FMOperatorType::Op1, FMOperatorType::Op2, FMOperatorType::Op3, FMOperatorType::Op4 }; // Property initialization helper inline auto makeEnvelopeFMSharedPtr(int n) { return std::make_shared(n); } inline auto makeLFOFMSharedPtr(int n) { return std::make_shared(n); } inline auto makeOperatorSequenceFMSharedPtr(int n) { return std::make_shared>(n, SequenceType::PlainSequence, FMOperatorSequenceUnit(0), FMOperatorSequenceUnit()); } inline auto makeArpeggioSharedPtr(int n) { return std::make_shared>(n, SequenceType::AbsoluteSequence, ArpeggioUnit(Note::DEFAULT_NOTE_NUM), ArpeggioUnit()); } inline auto makePitchSharedPtr(int n) { return std::make_shared>(n, SequenceType::AbsoluteSequence, PitchUnit(SEQ_PITCH_CENTER), PitchUnit()); } inline auto makePanSharedPtr(int n) { return std::make_shared>(n, SequenceType::AbsoluteSequence, PanUnit(PanType::CENTER), PanUnit()); } inline auto makeWaveformSSGSharedPtr(int n) { return std::make_shared>(n, SequenceType::PlainSequence, SSGWaveformUnit::makeOnlyDataUnit(SSGWaveformType::SQUARE), SSGWaveformUnit()); } inline auto makeToneNoiseSSGSharedPtr(int n) { return std::make_shared>(n, SequenceType::PlainSequence, SSGToneNoiseUnit(0), SSGToneNoiseUnit()); } inline auto makeEnvelopeSSGSharedPtr(int n) { return std::make_shared>(n, SequenceType::PlainSequence, SSGEnvelopeUnit::makeOnlyDataUnit(15), SSGEnvelopeUnit(), 15); } inline auto makeSampleADPCMSharedPtr(int n) { return std::make_shared(n); } inline auto makeEnvelopeADPCMSharedPtr(int n) { return std::make_shared>(n, SequenceType::PlainSequence, ADPCMEnvelopeUnit(255), ADPCMEnvelopeUnit(), 127); } // Utility functor template struct IsUsed { using ArgType = typename std::add_const::type::const_reference; bool operator()(ArgType prop) const { return prop->isUserInstrument(); } }; template struct IsEdited { using ArgType = typename std::add_const::type::const_reference; bool operator()(ArgType prop) const { return prop->isEdited(); } }; template struct IsUsedOrEdited { using ArgType = typename std::add_const::type::const_reference; bool operator()(ArgType prop) const { return (prop->isUserInstrument() || prop->isEdited()); } }; // Common process for properties template int cloneProperty(propArray& aryRef, int srcNum) { // Must find an unused property since the count of used properties <= the count of instruments auto&& it = std::find_if_not(aryRef.begin(), aryRef.end(), IsUsed()); int cloneNum = std::distance(aryRef.begin(), it); *it = aryRef.at(static_cast(srcNum))->clone(); (*it)->setNumber(cloneNum); return cloneNum; } template int findFirstAssignableProperty(propArray& aryRef, bool regardingUnedited, int startOffs = 0) { std::function cond; if (regardingUnedited) cond = IsUsedOrEdited(); else cond = IsUsed(); auto&& it = std::find_if_not(aryRef.cbegin() + startOffs, aryRef.cend(), cond); if (it == aryRef.cend()) return -1; if (!regardingUnedited) (*it)->clearParameters(); return std::distance(aryRef.cbegin(), it); } } InstrumentsManager::InstrumentsManager(bool unedited) : regardingUnedited_(unedited) { clearAll(); } void InstrumentsManager::addInstrument(int instNum, InstrumentType type, const std::string& name) { if (instNum < 0 || static_cast(insts_.size()) <= instNum) return; switch (type) { case InstrumentType::FM: { auto fm = new InstrumentFM(instNum, name, this); int envNum = findFirstAssignableEnvelopeFM(); if (envNum == -1) envNum = static_cast(envFM_.size()) - 1; fm->setEnvelopeNumber(envNum); envFM_.at(static_cast(envNum))->registerUserInstrument(instNum); int lfoNum = findFirstAssignableLFOFM(); if (lfoNum == -1) lfoNum = static_cast(lfoFM_.size()) - 1; fm->setLFONumber(lfoNum); fm->setLFOEnabled(false); for (auto param : ENV_FM_PARAMS) { int opSeqNum = findFirstAssignableOperatorSequenceFM(param); if (opSeqNum == -1) opSeqNum = static_cast(opSeqFM_.at(param).size()) - 1; fm->setOperatorSequenceNumber(param, opSeqNum); fm->setOperatorSequenceEnabled(param, false); } int arpNum = findFirstAssignableArpeggioFM(); if (arpNum == -1) arpNum = static_cast(arpFM_.size()) - 1; int ptNum = findFirstAssignablePitchFM(); if (ptNum == -1) ptNum = static_cast(ptFM_.size()) - 1; for (auto type : FM_OP_TYPES) { fm->setArpeggioNumber(type, arpNum); fm->setArpeggioEnabled(type, false); fm->setPitchNumber(type, ptNum); fm->setPitchEnabled(type, false); } int panNum = findFirstAssignablePanFM(); if (panNum == -1) panNum = static_cast(panFM_.size()) - 1; fm->setPanNumber(panNum); fm->setPanEnabled(false); insts_.at(static_cast(instNum)).reset(fm); break; } case InstrumentType::SSG: { auto ssg = new InstrumentSSG(instNum, name, this); int wfNum = findFirstAssignableWaveformSSG(); if (wfNum == -1) wfNum = static_cast(wfSSG_.size()) - 1; ssg->setWaveformNumber(wfNum); ssg->setWaveformEnabled(false); int tnNum = findFirstAssignableToneNoiseSSG(); if (tnNum == -1) tnNum = static_cast(tnSSG_.size()) - 1; ssg->setToneNoiseNumber(tnNum); ssg->setToneNoiseEnabled(false); int envNum = findFirstAssignableEnvelopeSSG(); if (envNum == -1) envNum = static_cast(envSSG_.size()) - 1; ssg->setEnvelopeNumber(envNum); ssg->setEnvelopeEnabled(false); int arpNum = findFirstAssignableArpeggioSSG(); if (arpNum == -1) arpNum = static_cast(arpSSG_.size()) - 1; ssg->setArpeggioNumber(arpNum); ssg->setArpeggioEnabled(false); int ptNum = findFirstAssignablePitchSSG(); if (ptNum == -1) ptNum = static_cast(ptSSG_.size()) - 1; ssg->setPitchNumber(ptNum); ssg->setPitchEnabled(false); insts_.at(static_cast(instNum)).reset(ssg); break; } case InstrumentType::ADPCM: { auto adpcm = new InstrumentADPCM(instNum, name, this); int sampNum = findFirstAssignableSampleADPCM(); if (sampNum == -1) sampNum = static_cast(sampADPCM_.size()) - 1; adpcm->setSampleNumber(sampNum); sampADPCM_.at(static_cast(sampNum))->registerUserInstrument(instNum); int envNum = findFirstAssignableEnvelopeADPCM(); if (envNum == -1) envNum = static_cast(envADPCM_.size()) - 1; adpcm->setEnvelopeNumber(envNum); adpcm->setEnvelopeEnabled(false); int arpNum = findFirstAssignableArpeggioADPCM(); if (arpNum == -1) arpNum = static_cast(arpADPCM_.size()) - 1; adpcm->setArpeggioNumber(arpNum); adpcm->setArpeggioEnabled(false); int ptNum = findFirstAssignablePitchADPCM(); if (ptNum == -1) ptNum = static_cast(ptADPCM_.size()) - 1; adpcm->setPitchNumber(ptNum); adpcm->setPitchEnabled(false); int panNum = findFirstAssignablePanADPCM(); if (panNum == -1) panNum = static_cast(panADPCM_.size()) - 1; adpcm->setPanNumber(panNum); adpcm->setPanEnabled(false); insts_.at(static_cast(instNum)).reset(adpcm); break; } case InstrumentType::Drumkit: { insts_.at(static_cast(instNum)).reset(new InstrumentDrumkit(instNum, name, this)); break; } default: throw std::invalid_argument("invalid instrument type"); } } void InstrumentsManager::addInstrument(AbstractInstrument* newInstPtr) { int num = newInstPtr->getNumber(); std::shared_ptr& inst = insts_.at(static_cast(num)); inst.reset(newInstPtr); switch (inst->getType()) { case InstrumentType::FM: { auto fm = std::dynamic_pointer_cast(inst); envFM_.at(static_cast(fm->getEnvelopeNumber()))->registerUserInstrument(num); if (fm->getLFOEnabled()) lfoFM_.at(static_cast(fm->getLFONumber()))->registerUserInstrument(num); for (auto p : ENV_FM_PARAMS) { if (fm->getOperatorSequenceEnabled(p)) opSeqFM_.at(p).at(static_cast(fm->getOperatorSequenceNumber(p))) ->registerUserInstrument(num); } for (auto t : FM_OP_TYPES) { if (fm->getArpeggioEnabled(t)) arpFM_.at(static_cast(fm->getArpeggioNumber(t)))->registerUserInstrument(num); if (fm->getPitchEnabled(t)) ptFM_.at(static_cast(fm->getPitchNumber(t)))->registerUserInstrument(num); } if (fm->getPanEnabled()) panFM_.at(static_cast(fm->getPanNumber()))->registerUserInstrument(num); break; } case InstrumentType::SSG: { auto ssg = std::dynamic_pointer_cast(inst); if (ssg->getWaveformEnabled()) wfSSG_.at(static_cast(ssg->getWaveformNumber()))->registerUserInstrument(num); if (ssg->getToneNoiseEnabled()) tnSSG_.at(static_cast(ssg->getToneNoiseNumber()))->registerUserInstrument(num); if (ssg->getEnvelopeEnabled()) envSSG_.at(static_cast(ssg->getEnvelopeNumber()))->registerUserInstrument(num); if (ssg->getArpeggioEnabled()) arpSSG_.at(static_cast(ssg->getArpeggioNumber()))->registerUserInstrument(num); if (ssg->getPitchEnabled()) ptSSG_.at(static_cast(ssg->getPitchNumber()))->registerUserInstrument(num); break; } case InstrumentType::ADPCM: { auto adpcm = std::dynamic_pointer_cast(inst); sampADPCM_.at(static_cast(adpcm->getSampleNumber()))->registerUserInstrument(num); if (adpcm->getEnvelopeEnabled()) envADPCM_.at(static_cast(adpcm->getEnvelopeNumber()))->registerUserInstrument(num); if (adpcm->getArpeggioEnabled()) arpADPCM_.at(static_cast(adpcm->getArpeggioNumber()))->registerUserInstrument(num); if (adpcm->getPitchEnabled()) ptADPCM_.at(static_cast(adpcm->getPitchNumber()))->registerUserInstrument(num); if (adpcm->getPanEnabled()) panADPCM_.at(static_cast(adpcm->getPanNumber()))->registerUserInstrument(num); break; } case InstrumentType::Drumkit: { auto kit = std::dynamic_pointer_cast(inst); for (const auto& key : kit->getAssignedKeys()) { sampADPCM_.at(static_cast(kit->getSampleNumber(key)))->registerUserInstrument(num); } break; } default: throw std::invalid_argument("invalid instrument type"); } } std::unique_ptr InstrumentsManager::removeInstrument(int instNum) { std::shared_ptr& inst = insts_.at(static_cast(instNum)); switch (inst->getType()) { case InstrumentType::FM: { auto fm = std::dynamic_pointer_cast(inst); envFM_.at(static_cast(fm->getEnvelopeNumber()))->deregisterUserInstrument(instNum); if (fm->getLFOEnabled()) lfoFM_.at(static_cast(fm->getLFONumber()))->deregisterUserInstrument(instNum); for (auto p : ENV_FM_PARAMS) { if (fm->getOperatorSequenceEnabled(p)) opSeqFM_.at(p).at(static_cast(fm->getOperatorSequenceNumber(p))) ->deregisterUserInstrument(instNum); } for (auto t : FM_OP_TYPES) { if (fm->getArpeggioEnabled(t)) arpFM_.at(static_cast(fm->getArpeggioNumber(t)))->deregisterUserInstrument(instNum); if (fm->getPitchEnabled(t)) ptFM_.at(static_cast(fm->getPitchNumber(t)))->deregisterUserInstrument(instNum); } if (fm->getPanEnabled()) panFM_.at(static_cast(fm->getPanNumber()))->deregisterUserInstrument(instNum); break; } case InstrumentType::SSG: { auto ssg = std::dynamic_pointer_cast(inst); if (ssg->getWaveformEnabled()) wfSSG_.at(static_cast(ssg->getWaveformNumber()))->deregisterUserInstrument(instNum); if (ssg->getToneNoiseEnabled()) tnSSG_.at(static_cast(ssg->getToneNoiseNumber()))->deregisterUserInstrument(instNum); if (ssg->getEnvelopeEnabled()) envSSG_.at(static_cast(ssg->getEnvelopeNumber()))->deregisterUserInstrument(instNum); if (ssg->getArpeggioEnabled()) arpSSG_.at(static_cast(ssg->getArpeggioNumber()))->deregisterUserInstrument(instNum); if (ssg->getPitchEnabled()) ptSSG_.at(static_cast(ssg->getPitchNumber()))->deregisterUserInstrument(instNum); break; } case InstrumentType::ADPCM: { auto adpcm = std::dynamic_pointer_cast(inst); sampADPCM_.at(static_cast(adpcm->getSampleNumber()))->deregisterUserInstrument(instNum); if (adpcm->getEnvelopeEnabled()) envADPCM_.at(static_cast(adpcm->getEnvelopeNumber()))->deregisterUserInstrument(instNum); if (adpcm->getArpeggioEnabled()) arpADPCM_.at(static_cast(adpcm->getArpeggioNumber()))->deregisterUserInstrument(instNum); if (adpcm->getPitchEnabled()) ptADPCM_.at(static_cast(adpcm->getPitchNumber()))->deregisterUserInstrument(instNum); if (adpcm->getPanEnabled()) panADPCM_.at(static_cast(adpcm->getPanNumber()))->deregisterUserInstrument(instNum); break; } case InstrumentType::Drumkit: { auto kit = std::dynamic_pointer_cast(inst); for (const int& key : kit->getAssignedKeys()) { sampADPCM_.at(static_cast(kit->getSampleNumber(key)))->deregisterUserInstrument(instNum); } break; } default: throw std::invalid_argument("invalid instrument type"); } std::unique_ptr clone(inst->clone()); inst.reset(); return clone; } void InstrumentsManager::cloneInstrument(int cloneInstNum, int refInstNum) { std::shared_ptr& refInst = insts_.at(static_cast(refInstNum)); addInstrument(cloneInstNum, refInst->getType(), refInst->getName()); switch (refInst->getType()) { case InstrumentType::FM: { auto refFm = std::dynamic_pointer_cast(refInst); auto cloneFm = std::dynamic_pointer_cast(insts_.at(static_cast(cloneInstNum))); setInstrumentFMEnvelope(cloneInstNum, refFm->getEnvelopeNumber()); setInstrumentFMLFO(cloneInstNum, refFm->getLFONumber()); if (refFm->getLFOEnabled()) setInstrumentFMLFOEnabled(cloneInstNum, true); for (auto p : ENV_FM_PARAMS) { setInstrumentFMOperatorSequence(cloneInstNum, p, refFm->getOperatorSequenceNumber(p)); if (refFm->getOperatorSequenceEnabled(p)) setInstrumentFMOperatorSequenceEnabled(cloneInstNum, p, true); } for (auto t : FM_OP_TYPES) { setInstrumentFMArpeggio(cloneInstNum, t, refFm->getArpeggioNumber(t)); if (refFm->getArpeggioEnabled(t)) setInstrumentFMArpeggioEnabled(cloneInstNum, t, true); setInstrumentFMPitch(cloneInstNum, t, refFm->getPitchNumber(t)); if (refFm->getPitchEnabled(t)) setInstrumentFMPitchEnabled(cloneInstNum, t, true); setInstrumentFMEnvelopeResetEnabled(cloneInstNum, t, refFm->getEnvelopeResetEnabled(t)); } break; } case InstrumentType::SSG: { auto refSsg = std::dynamic_pointer_cast(refInst); auto cloneSsg = std::dynamic_pointer_cast(insts_.at(static_cast(cloneInstNum))); setInstrumentSSGWaveform(cloneInstNum, refSsg->getWaveformNumber()); if (refSsg->getWaveformEnabled()) setInstrumentSSGWaveformEnabled(cloneInstNum, true); setInstrumentSSGToneNoise(cloneInstNum, refSsg->getToneNoiseNumber()); if (refSsg->getToneNoiseEnabled()) setInstrumentSSGToneNoiseEnabled(cloneInstNum, true); setInstrumentSSGEnvelope(cloneInstNum, refSsg->getEnvelopeNumber()); if (refSsg->getEnvelopeEnabled()) setInstrumentSSGEnvelopeEnabled(cloneInstNum, true); setInstrumentSSGArpeggio(cloneInstNum, refSsg->getArpeggioNumber()); if (refSsg->getArpeggioEnabled()) setInstrumentSSGArpeggioEnabled(cloneInstNum, true); setInstrumentSSGPitch(cloneInstNum, refSsg->getPitchNumber()); if (refSsg->getPitchEnabled()) setInstrumentSSGPitchEnabled(cloneInstNum, true); break; } case InstrumentType::ADPCM: { auto refAdpcm = std::dynamic_pointer_cast(refInst); auto cloneAdpcm = std::dynamic_pointer_cast(insts_.at(static_cast(cloneInstNum))); setInstrumentADPCMSample(cloneInstNum, refAdpcm->getSampleNumber()); setInstrumentADPCMEnvelope(cloneInstNum, refAdpcm->getEnvelopeNumber()); if (refAdpcm->getEnvelopeEnabled()) setInstrumentADPCMEnvelopeEnabled(cloneInstNum, true); setInstrumentADPCMArpeggio(cloneInstNum, refAdpcm->getArpeggioNumber()); if (refAdpcm->getArpeggioEnabled()) setInstrumentADPCMArpeggioEnabled(cloneInstNum, true); setInstrumentADPCMPitch(cloneInstNum, refAdpcm->getPitchNumber()); if (refAdpcm->getPitchEnabled()) setInstrumentADPCMPitchEnabled(cloneInstNum, true); break; } case InstrumentType::Drumkit: { auto refKit = std::dynamic_pointer_cast(refInst); auto cloneKit = std::dynamic_pointer_cast(insts_.at(static_cast(cloneInstNum))); for (const int& key : refKit->getAssignedKeys()) { setInstrumentDrumkitSamples(cloneInstNum, key, refKit->getSampleNumber(key)); setInstrumentDrumkitPitch(cloneInstNum, key, refKit->getPitch(key)); setInstrumentDrumkitPan(cloneInstNum, key, refKit->getPan(key)); } break; } default: throw std::invalid_argument("invalid instrument type"); } } void InstrumentsManager::deepCloneInstrument(int cloneInstNum, int refInstNum) { std::shared_ptr& refInst = insts_.at(static_cast(refInstNum)); addInstrument(cloneInstNum, refInst->getType(), refInst->getName()); switch (refInst->getType()) { case InstrumentType::FM: { auto refFm = std::dynamic_pointer_cast(refInst); auto cloneFm = std::dynamic_pointer_cast(insts_.at(static_cast(cloneInstNum))); envFM_[static_cast(cloneFm->getEnvelopeNumber())]->deregisterUserInstrument(cloneInstNum); // Remove temporary number int envNum = cloneProperty(envFM_, refFm->getEnvelopeNumber()); cloneFm->setEnvelopeNumber(envNum); envFM_[static_cast(envNum)]->registerUserInstrument(cloneInstNum); if (refFm->getLFOEnabled()) { cloneFm->setLFOEnabled(true); int lfoNum = cloneProperty(lfoFM_, refFm->getLFONumber()); cloneFm->setLFONumber(lfoNum); lfoFM_[static_cast(lfoNum)]->registerUserInstrument(cloneInstNum); } for (auto envParam : ENV_FM_PARAMS) { if (refFm->getOperatorSequenceEnabled(envParam)) { cloneFm->setOperatorSequenceEnabled(envParam, true); int opSeqNum = cloneProperty(opSeqFM_.at(envParam), refFm->getOperatorSequenceNumber(envParam)); cloneFm->setOperatorSequenceNumber(envParam, opSeqNum); opSeqFM_.at(envParam)[static_cast(opSeqNum)]->registerUserInstrument(cloneInstNum); } } std::unordered_map arpCloneMap; for (auto opType : FM_OP_TYPES) { if (refFm->getArpeggioEnabled(opType)) { cloneFm->setArpeggioEnabled(opType, true); int srcNum = refFm->getArpeggioNumber(opType); if (!arpCloneMap.count(srcNum)) { arpCloneMap[srcNum] = cloneProperty(arpFM_, srcNum); } cloneFm->setArpeggioNumber(opType, arpCloneMap[srcNum]); arpFM_[static_cast(arpCloneMap[srcNum])]->registerUserInstrument(cloneInstNum); } } std::unordered_map ptCloneMap; for (auto opType : FM_OP_TYPES) { if (refFm->getPitchEnabled(opType)) { cloneFm->setPitchEnabled(opType, true); int srcNum = refFm->getPitchNumber(opType); if (!ptCloneMap.count(srcNum)) { ptCloneMap[srcNum] = cloneProperty(ptFM_, srcNum); } cloneFm->setPitchNumber(opType, ptCloneMap[srcNum]); ptFM_[static_cast(ptCloneMap[srcNum])]->registerUserInstrument(cloneInstNum); } } if (refFm->getPanEnabled()) { cloneFm->setPanEnabled(true); int panNum = cloneProperty(panFM_, refFm->getPanNumber()); cloneFm->setPanNumber(panNum); panFM_[static_cast(panNum)]->registerUserInstrument(cloneInstNum); } for (auto t : FM_OP_TYPES) setInstrumentFMEnvelopeResetEnabled(cloneInstNum, t, refFm->getEnvelopeResetEnabled(t)); break; } case InstrumentType::SSG: { auto refSsg = std::dynamic_pointer_cast(refInst); auto cloneSsg = std::dynamic_pointer_cast(insts_.at(static_cast(cloneInstNum))); if (refSsg->getWaveformEnabled()) { cloneSsg->setWaveformEnabled(true); int wfNum = cloneProperty(wfSSG_, refSsg->getWaveformNumber()); cloneSsg->setWaveformNumber(wfNum); wfSSG_[static_cast(wfNum)]->registerUserInstrument(cloneInstNum); } if (refSsg->getToneNoiseEnabled()) { cloneSsg->setToneNoiseEnabled(true); int tnNum = cloneProperty(tnSSG_, refSsg->getToneNoiseNumber()); cloneSsg->setToneNoiseNumber(tnNum); tnSSG_[static_cast(tnNum)]->registerUserInstrument(cloneInstNum); } if (refSsg->getEnvelopeEnabled()) { cloneSsg->setEnvelopeEnabled(true); int envNum = cloneProperty(envSSG_, refSsg->getEnvelopeNumber()); cloneSsg->setEnvelopeNumber(envNum); envSSG_[static_cast(envNum)]->registerUserInstrument(cloneInstNum); } if (refSsg->getArpeggioEnabled()) { cloneSsg->setArpeggioEnabled(true); int arpNum = cloneProperty(arpSSG_, refSsg->getArpeggioNumber()); cloneSsg->setArpeggioNumber(arpNum); arpSSG_[static_cast(arpNum)]->registerUserInstrument(cloneInstNum); } if (refSsg->getPitchEnabled()) { cloneSsg->setPitchEnabled(true); int ptNum = cloneProperty(ptSSG_, refSsg->getPitchNumber()); cloneSsg->setPitchNumber(ptNum); ptSSG_[static_cast(ptNum)]->registerUserInstrument(cloneInstNum); } break; } case InstrumentType::ADPCM: { auto refAdpcm = std::dynamic_pointer_cast(refInst); auto cloneAdpcm = std::dynamic_pointer_cast(insts_.at(static_cast(cloneInstNum))); sampADPCM_[static_cast(cloneAdpcm->getSampleNumber())]->deregisterUserInstrument(cloneInstNum); // Remove temporary number int sampNum = cloneProperty(sampADPCM_, refAdpcm->getSampleNumber()); cloneAdpcm->setSampleNumber(sampNum); sampADPCM_[static_cast(sampNum)]->registerUserInstrument(cloneInstNum); if (refAdpcm->getEnvelopeEnabled()) { cloneAdpcm->setEnvelopeEnabled(true); int envNum = cloneProperty(envADPCM_, refAdpcm->getEnvelopeNumber()); cloneAdpcm->setEnvelopeNumber(envNum); envADPCM_[static_cast(envNum)]->registerUserInstrument(cloneInstNum); } if (refAdpcm->getArpeggioEnabled()) { cloneAdpcm->setArpeggioEnabled(true); int arpNum = cloneProperty(arpADPCM_, refAdpcm->getArpeggioNumber()); cloneAdpcm->setArpeggioNumber(arpNum); arpADPCM_[static_cast(arpNum)]->registerUserInstrument(cloneInstNum); } if (refAdpcm->getPitchEnabled()) { cloneAdpcm->setPitchEnabled(true); int ptNum = cloneProperty(ptADPCM_, refAdpcm->getPitchNumber()); cloneAdpcm->setPitchNumber(ptNum); ptADPCM_[static_cast(ptNum)]->registerUserInstrument(cloneInstNum); } if (refAdpcm->getPanEnabled()) { cloneAdpcm->setPanEnabled(true); int panNum = cloneProperty(panADPCM_, refAdpcm->getPanNumber()); cloneAdpcm->setPanNumber(panNum); panADPCM_[static_cast(panNum)]->registerUserInstrument(cloneInstNum); } break; } case InstrumentType::Drumkit: { auto refKit = std::dynamic_pointer_cast(refInst); auto cloneKit = std::dynamic_pointer_cast(insts_.at(static_cast(cloneInstNum))); std::unordered_map sampCloneMap; for (const int& key : refKit->getAssignedKeys()) { int srcNum = refKit->getSampleNumber(key); if (!sampCloneMap.count(srcNum)) sampCloneMap[srcNum] = cloneProperty(sampADPCM_, srcNum); cloneKit->setSampleNumber(key, sampCloneMap[srcNum]); sampADPCM_[static_cast(sampCloneMap[srcNum])]->registerUserInstrument(cloneInstNum); setInstrumentDrumkitPitch(cloneInstNum, key, refKit->getPitch(key)); setInstrumentDrumkitPan(cloneInstNum, key, refKit->getPan(key)); } break; } default: throw std::invalid_argument("invalid instrument type"); } } void InstrumentsManager::swapInstruments(int inst1Num, int inst2Num) { std::unique_ptr inst1 = removeInstrument(inst1Num); std::unique_ptr inst2 = removeInstrument(inst2Num); inst1->setNumber(inst2Num); inst2->setNumber(inst1Num); addInstrument(inst1.release()); addInstrument(inst2.release()); } /// [Return] instrument shared pointer or nullptr std::shared_ptr InstrumentsManager::getInstrumentSharedPtr(int instNum) { if (0 <= instNum && instNum < static_cast(insts_.size())) return insts_[static_cast(instNum)]; else return std::shared_ptr(); } void InstrumentsManager::clearAll() { for (auto p : ENV_FM_PARAMS) { opSeqFM_.emplace(p, std::array>, 128>()); } for (size_t i = 0; i < 128; ++i) { insts_[i].reset(); envFM_[i] = makeEnvelopeFMSharedPtr(i); lfoFM_[i] = makeLFOFMSharedPtr(i); for (auto& p : opSeqFM_) p.second[i] = makeOperatorSequenceFMSharedPtr(i); arpFM_[i] = makeArpeggioSharedPtr(i); ptFM_[i] = makePitchSharedPtr(i); panFM_[i] = makePanSharedPtr(i); wfSSG_[i] = makeWaveformSSGSharedPtr(i); tnSSG_[i] = makeToneNoiseSSGSharedPtr(i); envSSG_[i] = makeEnvelopeSSGSharedPtr(i); arpSSG_[i] = makeArpeggioSharedPtr(i); ptSSG_[i] = makePitchSharedPtr(i); sampADPCM_[i] = makeSampleADPCMSharedPtr(i); envADPCM_[i] = makeEnvelopeADPCMSharedPtr(i); arpADPCM_[i] = makeArpeggioSharedPtr(i); ptADPCM_[i] = makePitchSharedPtr(i); panADPCM_[i] = makePanSharedPtr(i); } } std::vector InstrumentsManager::getInstrumentIndices() const { return utils::findIndicesIf(insts_, [](decltype(insts_)::const_reference inst) { return inst; }); } void InstrumentsManager::setInstrumentName(int instNum, const std::string& name) { insts_.at(static_cast(instNum))->setName(name); } std::string InstrumentsManager::getInstrumentName(int instNum) const { return insts_.at(static_cast(instNum))->getName(); } std::vector InstrumentsManager::getInstrumentNameList() const { using InstRef = decltype(insts_)::const_reference; return utils::transformIf(insts_, [](InstRef inst) { return inst; }, [](InstRef inst) { return inst->getName(); }); } void InstrumentsManager::clearUnusedInstrumentProperties() { for (size_t i = 0; i < 128; ++i) { if (!envFM_[i]->isUserInstrument()) envFM_[i] = makeEnvelopeFMSharedPtr(i); if (!lfoFM_[i]->isUserInstrument()) lfoFM_[i] = makeLFOFMSharedPtr(i); for (auto& p : opSeqFM_) { if (!p.second[i]->isUserInstrument()) p.second[i] = makeOperatorSequenceFMSharedPtr(i); } if (!arpFM_[i]->isUserInstrument()) arpFM_[i] = makeArpeggioSharedPtr(i); if (!ptFM_[i]->isUserInstrument()) ptFM_[i] = makePitchSharedPtr(i); if (!panFM_[i]->isUserInstrument()) panFM_[i] = makePanSharedPtr(i); if (!wfSSG_[i]->isUserInstrument()) wfSSG_[i] = makeWaveformSSGSharedPtr(i); if (!tnSSG_[i]->isUserInstrument()) tnSSG_[i] = makeToneNoiseSSGSharedPtr(i); if (!envSSG_[i]->isUserInstrument()) envSSG_[i] = makeEnvelopeSSGSharedPtr(i); if (!arpSSG_[i]->isUserInstrument()) arpSSG_[i] = makeArpeggioSharedPtr(i); if (!ptSSG_[i]->isUserInstrument()) ptSSG_[i] = makePitchSharedPtr(i); if (!sampADPCM_[i]->isUserInstrument()) sampADPCM_[i] = makeSampleADPCMSharedPtr(i); if (!envADPCM_[i]->isUserInstrument()) envADPCM_[i] = makeEnvelopeADPCMSharedPtr(i); if (!arpADPCM_[i]->isUserInstrument()) arpADPCM_[i] = makeArpeggioSharedPtr(i); if (!ptADPCM_[i]->isUserInstrument()) ptADPCM_[i] = makePitchSharedPtr(i); if (!panADPCM_[i]->isUserInstrument()) panADPCM_[i] = makePanSharedPtr(i); } } /// [Return] /// -1: no free instrument /// else: first free instrument number int InstrumentsManager::findFirstFreeInstrument() const { auto&& it = std::find_if_not(insts_.cbegin(), insts_.cend(), [](const std::shared_ptr& inst) { return inst; }); return (it == insts_.cend() ? -1 : std::distance(insts_.cbegin(), it)); } std::unordered_map InstrumentsManager::getDuplicateInstrumentMap() const { std::unordered_map dupMap; std::vector idcs = getInstrumentIndices(); static const std::unordered_map, std::shared_ptr) const> eqCheck = { { InstrumentType::FM, &InstrumentsManager::equalPropertiesFM }, { InstrumentType::SSG, &InstrumentsManager::equalPropertiesSSG }, { InstrumentType::ADPCM, &InstrumentsManager::equalPropertiesADPCM }, { InstrumentType::Drumkit, &InstrumentsManager::equalPropertiesDrumkit } }; for (size_t i = 0; i < idcs.size(); ++i) { int baseIdx = idcs[i]; std::shared_ptr base = insts_[baseIdx]; for (size_t j = i + 1; j < idcs.size();) { int tgtIdx = idcs[j]; std::shared_ptr tgt = insts_[tgtIdx]; if (base->getType() == tgt->getType() && (this->*eqCheck.at(base->getType()))(base, tgt)) { dupMap[tgtIdx] = baseIdx; idcs.erase(idcs.begin() + j); continue; } ++j; } } return dupMap; } //----- FM methods ----- void InstrumentsManager::setInstrumentFMEnvelope(int instNum, int envNum) { auto fm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); envFM_.at(static_cast(fm->getEnvelopeNumber()))->deregisterUserInstrument(instNum); envFM_.at(static_cast(envNum))->registerUserInstrument(instNum); fm->setEnvelopeNumber(envNum); } int InstrumentsManager::getInstrumentFMEnvelope(int instNum) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getEnvelopeNumber(); } void InstrumentsManager::setEnvelopeFMParameter(int envNum, FMEnvelopeParameter param, int value) { envFM_.at(static_cast(envNum))->setParameterValue(param, value); } int InstrumentsManager::getEnvelopeFMParameter(int envNum, FMEnvelopeParameter param) const { return envFM_.at(static_cast(envNum))->getParameterValue(param); } void InstrumentsManager::setEnvelopeFMOperatorEnabled(int envNum, int opNum, bool enabled) { envFM_.at(static_cast(envNum))->setOperatorEnabled(opNum, enabled); } bool InstrumentsManager::getEnvelopeFMOperatorEnabled(int envNum, int opNum) const { return envFM_.at(static_cast(envNum))->getOperatorEnabled(opNum); } std::multiset InstrumentsManager::getEnvelopeFMUsers(int envNum) const { return envFM_.at(static_cast(envNum))->getUserInstruments(); } std::vector InstrumentsManager::getEnvelopeFMEntriedIndices() const { return utils::findIndicesIf(envFM_, IsEdited()); } int InstrumentsManager::findFirstAssignableEnvelopeFM() const { return findFirstAssignableProperty(envFM_, regardingUnedited_); } void InstrumentsManager::setInstrumentFMLFOEnabled(int instNum, bool enabled) { auto fm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); fm->setLFOEnabled(enabled); if (enabled) lfoFM_.at(static_cast(fm->getLFONumber()))->registerUserInstrument(instNum); else lfoFM_.at(static_cast(fm->getLFONumber()))->deregisterUserInstrument(instNum); } bool InstrumentsManager::getInstrumentFMLFOEnabled(int instNum) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getLFOEnabled(); } void InstrumentsManager::setInstrumentFMLFO(int instNum, int lfoNum) { auto fm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (fm->getLFOEnabled()) { lfoFM_.at(static_cast(fm->getLFONumber()))->deregisterUserInstrument(instNum); lfoFM_.at(static_cast(lfoNum))->registerUserInstrument(instNum); } fm->setLFONumber(lfoNum); } int InstrumentsManager::getInstrumentFMLFO(int instNum) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getLFONumber(); } void InstrumentsManager::setLFOFMParameter(int lfoNum, FMLFOParameter param, int value) { lfoFM_.at(static_cast(lfoNum))->setParameterValue(param, value); } int InstrumentsManager::getLFOFMparameter(int lfoNum, FMLFOParameter param) const { return lfoFM_.at(static_cast(lfoNum))->getParameterValue(param); } std::multiset InstrumentsManager::getLFOFMUsers(int lfoNum) const { return lfoFM_.at(static_cast(lfoNum))->getUserInstruments(); } std::vector InstrumentsManager::getLFOFMEntriedIndices() const { return utils::findIndicesIf(lfoFM_, IsEdited()); } int InstrumentsManager::findFirstAssignableLFOFM() const { return findFirstAssignableProperty(lfoFM_, regardingUnedited_); } void InstrumentsManager::setInstrumentFMOperatorSequenceEnabled(int instNum, FMEnvelopeParameter param, bool enabled) { auto fm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); fm->setOperatorSequenceEnabled(param, enabled); if (enabled) opSeqFM_.at(param).at(static_cast(fm->getOperatorSequenceNumber(param)))->registerUserInstrument(instNum); else opSeqFM_.at(param).at(static_cast(fm->getOperatorSequenceNumber(param)))->deregisterUserInstrument(instNum); } bool InstrumentsManager::getInstrumentFMOperatorSequenceEnabled(int instNum, FMEnvelopeParameter param) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getOperatorSequenceEnabled(param); } void InstrumentsManager::setInstrumentFMOperatorSequence(int instNum, FMEnvelopeParameter param, int opSeqNum) { auto fm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (fm->getOperatorSequenceEnabled(param)) { opSeqFM_.at(param).at(static_cast(fm->getOperatorSequenceNumber(param)))->deregisterUserInstrument(instNum); opSeqFM_.at(param).at(static_cast(opSeqNum))->registerUserInstrument(instNum); } fm->setOperatorSequenceNumber(param, opSeqNum); } int InstrumentsManager::getInstrumentFMOperatorSequence(int instNum, FMEnvelopeParameter param) { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getOperatorSequenceNumber(param); } void InstrumentsManager::addOperatorSequenceFMSequenceData(FMEnvelopeParameter param, int opSeqNum, int data) { opSeqFM_.at(param).at(static_cast(opSeqNum))->addSequenceUnit(FMOperatorSequenceUnit(data)); } void InstrumentsManager::removeOperatorSequenceFMSequenceData(FMEnvelopeParameter param, int opSeqNum) { opSeqFM_.at(param).at(static_cast(opSeqNum))->removeSequenceUnit(); } void InstrumentsManager::setOperatorSequenceFMSequenceData(FMEnvelopeParameter param, int opSeqNum, int cnt, int data) { opSeqFM_.at(param).at(static_cast(opSeqNum))->setSequenceUnit(cnt, FMOperatorSequenceUnit(data)); } std::vector InstrumentsManager::getOperatorSequenceFMSequence(FMEnvelopeParameter param, int opSeqNum) { return opSeqFM_.at(param).at(static_cast(opSeqNum))->getSequence(); } void InstrumentsManager::addOperatorSequenceFMLoop(FMEnvelopeParameter param, int opSeqNum, const InstrumentSequenceLoop& loop) { opSeqFM_.at(param).at(static_cast(opSeqNum))->addLoop(loop); } void InstrumentsManager::removeOperatorSequenceFMLoop(FMEnvelopeParameter param, int opSeqNum, int begin, int end) { opSeqFM_.at(param).at(static_cast(opSeqNum))->removeLoop(begin, end); } void InstrumentsManager::changeOperatorSequenceFMLoop(FMEnvelopeParameter param, int opSeqNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { opSeqFM_.at(param).at(static_cast(opSeqNum))->changeLoop(prevBegin, prevEnd, loop); } void InstrumentsManager::clearOperatorSequenceFMLoops(FMEnvelopeParameter param, int opSeqNum) { opSeqFM_.at(param).at(static_cast(opSeqNum))->clearLoops(); } InstrumentSequenceLoopRoot InstrumentsManager::getOperatorSequenceFMLoopRoot(FMEnvelopeParameter param, int opSeqNum) const { return opSeqFM_.at(param).at(static_cast(opSeqNum))->getLoopRoot(); } void InstrumentsManager::setOperatorSequenceFMRelease(FMEnvelopeParameter param, int opSeqNum, const InstrumentSequenceRelease& release) { opSeqFM_.at(param).at(static_cast(opSeqNum))->setRelease(release); } InstrumentSequenceRelease InstrumentsManager::getOperatorSequenceFMRelease(FMEnvelopeParameter param, int opSeqNum) const { return opSeqFM_.at(param).at(static_cast(opSeqNum))->getRelease(); } FMOperatorSequenceIter InstrumentsManager::getOperatorSequenceFMIterator(FMEnvelopeParameter param, int opSeqNum) const { return opSeqFM_.at(param).at(static_cast(opSeqNum))->getIterator(); } std::multiset InstrumentsManager::getOperatorSequenceFMUsers(FMEnvelopeParameter param, int opSeqNum) const { return opSeqFM_.at(param).at(static_cast(opSeqNum))->getUserInstruments(); } std::vector InstrumentsManager::getOperatorSequenceFMEntriedIndices(FMEnvelopeParameter param) const { return utils::findIndicesIf(opSeqFM_.at(param), IsUsedOrEdited()); } int InstrumentsManager::findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter param) const { return findFirstAssignableProperty(opSeqFM_.at(param), regardingUnedited_); } void InstrumentsManager::setInstrumentFMArpeggioEnabled(int instNum, FMOperatorType op, bool enabled) { auto fm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); fm->setArpeggioEnabled(op, enabled); if (enabled) arpFM_.at(static_cast(fm->getArpeggioNumber(op)))->registerUserInstrument(instNum); else arpFM_.at(static_cast(fm->getArpeggioNumber(op)))->deregisterUserInstrument(instNum); } bool InstrumentsManager::getInstrumentFMArpeggioEnabled(int instNum, FMOperatorType op) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getArpeggioEnabled(op); } void InstrumentsManager::setInstrumentFMArpeggio(int instNum, FMOperatorType op, int arpNum) { auto fm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (fm->getArpeggioEnabled(op)) { arpFM_.at(static_cast(fm->getArpeggioNumber(op)))->deregisterUserInstrument(instNum); arpFM_.at(static_cast(arpNum))->registerUserInstrument(instNum); } fm->setArpeggioNumber(op, arpNum); } int InstrumentsManager::getInstrumentFMArpeggio(int instNum, FMOperatorType op) { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getArpeggioNumber(op); } void InstrumentsManager::setArpeggioFMType(int arpNum, SequenceType type) { arpFM_.at(static_cast(arpNum))->setType(type); } SequenceType InstrumentsManager::getArpeggioFMType(int arpNum) const { return arpFM_.at(static_cast(arpNum))->getType(); } void InstrumentsManager::addArpeggioFMSequenceData(int arpNum, int data) { arpFM_.at(static_cast(arpNum))->addSequenceUnit(ArpeggioUnit(data)); } void InstrumentsManager::removeArpeggioFMSequenceData(int arpNum) { arpFM_.at(static_cast(arpNum))->removeSequenceUnit(); } void InstrumentsManager::setArpeggioFMSequenceData(int arpNum, int cnt, int data) { arpFM_.at(static_cast(arpNum))->setSequenceUnit(cnt, ArpeggioUnit(data)); } std::vector InstrumentsManager::getArpeggioFMSequence(int arpNum) { return arpFM_.at(static_cast(arpNum))->getSequence(); } void InstrumentsManager::addArpeggioFMLoop(int arpNum, const InstrumentSequenceLoop& loop) { arpFM_.at(static_cast(arpNum))->addLoop(loop); } void InstrumentsManager::removeArpeggioFMLoop(int arpNum, int begin, int end) { arpFM_.at(static_cast(arpNum))->removeLoop(begin, end); } void InstrumentsManager::changeArpeggioFMLoop(int arpNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { arpFM_.at(static_cast(arpNum))->changeLoop(prevBegin, prevEnd, loop); } void InstrumentsManager::clearArpeggioFMLoops(int arpNum) { arpFM_.at(static_cast(arpNum))->clearLoops(); } InstrumentSequenceLoopRoot InstrumentsManager::getArpeggioFMLoopRoot(int arpNum) const { return arpFM_.at(static_cast(arpNum))->getLoopRoot(); } void InstrumentsManager::setArpeggioFMRelease(int arpNum, const InstrumentSequenceRelease& release) { arpFM_.at(static_cast(arpNum))->setRelease(release); } InstrumentSequenceRelease InstrumentsManager::getArpeggioFMRelease(int arpNum) const { return arpFM_.at(static_cast(arpNum))->getRelease(); } ArpeggioIter InstrumentsManager::getArpeggioFMIterator(int arpNum) const { return arpFM_.at(static_cast(arpNum))->getIterator(); } std::multiset InstrumentsManager::getArpeggioFMUsers(int arpNum) const { return arpFM_.at(static_cast(arpNum))->getUserInstruments(); } std::vector InstrumentsManager::getArpeggioFMEntriedIndices() const { return utils::findIndicesIf(arpFM_, IsUsedOrEdited()); } int InstrumentsManager::findFirstAssignableArpeggioFM() const { return findFirstAssignableProperty(arpFM_, regardingUnedited_); } void InstrumentsManager::setInstrumentFMPitchEnabled(int instNum, FMOperatorType op, bool enabled) { auto fm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); fm->setPitchEnabled(op, enabled); if (enabled) ptFM_.at(static_cast(fm->getPitchNumber(op)))->registerUserInstrument(instNum); else ptFM_.at(static_cast(fm->getPitchNumber(op)))->deregisterUserInstrument(instNum); } bool InstrumentsManager::getInstrumentFMPitchEnabled(int instNum, FMOperatorType op) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getPitchEnabled(op); } void InstrumentsManager::setInstrumentFMPitch(int instNum, FMOperatorType op, int ptNum) { auto fm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (fm->getPitchEnabled(op)) { ptFM_.at(static_cast(fm->getPitchNumber(op)))->deregisterUserInstrument(instNum); ptFM_.at(static_cast(ptNum))->registerUserInstrument(instNum); } fm->setPitchNumber(op, ptNum); } int InstrumentsManager::getInstrumentFMPitch(int instNum, FMOperatorType op) { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getPitchNumber(op); } void InstrumentsManager::setPitchFMType(int ptNum, SequenceType type) { ptFM_.at(static_cast(ptNum))->setType(type); } SequenceType InstrumentsManager::getPitchFMType(int ptNum) const { return ptFM_.at(static_cast(ptNum))->getType(); } void InstrumentsManager::addPitchFMSequenceData(int ptNum, int data) { ptFM_.at(static_cast(ptNum))->addSequenceUnit(PitchUnit(data)); } void InstrumentsManager::removePitchFMSequenceData(int ptNum) { ptFM_.at(static_cast(ptNum))->removeSequenceUnit(); } void InstrumentsManager::setPitchFMSequenceData(int ptNum, int cnt, int data) { ptFM_.at(static_cast(ptNum))->setSequenceUnit(cnt, PitchUnit(data)); } std::vector InstrumentsManager::getPitchFMSequence(int ptNum) { return ptFM_.at(static_cast(ptNum))->getSequence(); } void InstrumentsManager::addPitchFMLoop(int ptNum, const InstrumentSequenceLoop& loop) { ptFM_.at(static_cast(ptNum))->addLoop(loop); } void InstrumentsManager::removePitchFMLoop(int ptNum, int begin, int end) { ptFM_.at(static_cast(ptNum))->removeLoop(begin, end); } void InstrumentsManager::changePitchFMLoop(int ptNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { ptFM_.at(static_cast(ptNum))->changeLoop(prevBegin, prevEnd, loop); } void InstrumentsManager::clearPitchFMLoops(int ptNum) { ptFM_.at(static_cast(ptNum))->clearLoops(); } InstrumentSequenceLoopRoot InstrumentsManager::getPitchFMLoopRoot(int ptNum) const { return ptFM_.at(static_cast(ptNum))->getLoopRoot(); } void InstrumentsManager::setPitchFMRelease(int ptNum, const InstrumentSequenceRelease& release) { ptFM_.at(static_cast(ptNum))->setRelease(release); } InstrumentSequenceRelease InstrumentsManager::getPitchFMRelease(int ptNum) const { return ptFM_.at(static_cast(ptNum))->getRelease(); } PitchIter InstrumentsManager::getPitchFMIterator(int ptNum) const { return ptFM_.at(static_cast(ptNum))->getIterator(); } std::multiset InstrumentsManager::getPitchFMUsers(int ptNum) const { return ptFM_.at(static_cast(ptNum))->getUserInstruments(); } std::vector InstrumentsManager::getPitchFMEntriedIndices() const { return utils::findIndicesIf(ptFM_, IsUsedOrEdited()); } int InstrumentsManager::findFirstAssignablePitchFM() const { return findFirstAssignableProperty(ptFM_, regardingUnedited_); } void InstrumentsManager::setInstrumentFMPanEnabled(int instNum, bool enabled) { auto fm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); fm->setPanEnabled(enabled); if (enabled) panFM_.at(static_cast(fm->getPanNumber()))->registerUserInstrument(instNum); else panFM_.at(static_cast(fm->getPanNumber()))->deregisterUserInstrument(instNum); } bool InstrumentsManager::getInstrumentFMPanEnabled(int instNum) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getPanEnabled(); } void InstrumentsManager::setInstrumentFMPan(int instNum, int panNum) { auto fm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (fm->getPanEnabled()) { panFM_.at(static_cast(fm->getPanNumber()))->deregisterUserInstrument(instNum); panFM_.at(static_cast(panNum))->registerUserInstrument(instNum); } fm->setPanNumber(panNum); } int InstrumentsManager::getInstrumentFMPan(int instNum) { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getPanNumber(); } void InstrumentsManager::addPanFMSequenceData(int panNum, int data) { panFM_.at(static_cast(panNum))->addSequenceUnit(PanUnit(data)); } void InstrumentsManager::removePanFMSequenceData(int panNum) { panFM_.at(static_cast(panNum))->removeSequenceUnit(); } void InstrumentsManager::setPanFMSequenceData(int panNum, int cnt, int data) { panFM_.at(static_cast(panNum))->setSequenceUnit(cnt, PanUnit(data)); } std::vector InstrumentsManager::getPanFMSequence(int panNum) { return panFM_.at(static_cast(panNum))->getSequence(); } void InstrumentsManager::addPanFMLoop(int panNum, const InstrumentSequenceLoop& loop) { panFM_.at(static_cast(panNum))->addLoop(loop); } void InstrumentsManager::removePanFMLoop(int panNum, int begin, int end) { panFM_.at(static_cast(panNum))->removeLoop(begin, end); } void InstrumentsManager::changePanFMLoop(int panNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { panFM_.at(static_cast(panNum))->changeLoop(prevBegin, prevEnd, loop); } void InstrumentsManager::clearPanFMLoops(int panNum) { panFM_.at(static_cast(panNum))->clearLoops(); } InstrumentSequenceLoopRoot InstrumentsManager::getPanFMLoopRoot(int panNum) const { return panFM_.at(static_cast(panNum))->getLoopRoot(); } void InstrumentsManager::setPanFMRelease(int panNum, const InstrumentSequenceRelease& release) { panFM_.at(static_cast(panNum))->setRelease(release); } InstrumentSequenceRelease InstrumentsManager::getPanFMRelease(int panNum) const { return panFM_.at(static_cast(panNum))->getRelease(); } PanIter InstrumentsManager::getPanFMIterator(int panNum) const { return panFM_.at(static_cast(panNum))->getIterator(); } std::multiset InstrumentsManager::getPanFMUsers(int panNum) const { return panFM_.at(static_cast(panNum))->getUserInstruments(); } std::vector InstrumentsManager::getPanFMEntriedIndices() const { return utils::findIndicesIf(panFM_, IsUsedOrEdited()); } int InstrumentsManager::findFirstAssignablePanFM() const { return findFirstAssignableProperty(panFM_, regardingUnedited_); } void InstrumentsManager::setInstrumentFMEnvelopeResetEnabled(int instNum, FMOperatorType op, bool enabled) { std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->setEnvelopeResetEnabled(op, enabled); } bool InstrumentsManager::equalPropertiesFM(std::shared_ptr a, std::shared_ptr b) const { auto aFm = std::dynamic_pointer_cast(a); auto bFm = std::dynamic_pointer_cast(b); if (*envFM_[aFm->getEnvelopeNumber()] != *envFM_[bFm->getEnvelopeNumber()]) return false; if (aFm->getLFOEnabled() != bFm->getLFOEnabled()) return false; if (aFm->getLFOEnabled() && *lfoFM_[aFm->getLFONumber()] != *lfoFM_[bFm->getLFONumber()]) return false; for (auto& pair : opSeqFM_) { if (aFm->getOperatorSequenceEnabled(pair.first) != bFm->getOperatorSequenceEnabled(pair.first)) return false; if (aFm->getOperatorSequenceEnabled(pair.first) && *pair.second[aFm->getOperatorSequenceNumber(pair.first)] != *pair.second[bFm->getOperatorSequenceNumber(pair.first)]) return false; } for (auto& type : FM_OP_TYPES) { if (aFm->getArpeggioEnabled(type) != bFm->getArpeggioEnabled(type)) return false; if (aFm->getArpeggioEnabled(type) && *arpFM_[aFm->getArpeggioNumber(type)] != *arpFM_[bFm->getArpeggioNumber(type)]) return false; if (aFm->getPitchEnabled(type) != bFm->getPitchEnabled(type)) return false; if (aFm->getPitchEnabled(type) && *ptFM_[aFm->getPitchNumber(type)] != *ptFM_[bFm->getPitchNumber(type)]) return false; if (aFm->getEnvelopeResetEnabled(type) != bFm->getEnvelopeResetEnabled(type)) return false; } if (aFm->getPanEnabled() && *panFM_[aFm->getPanNumber()] != *panFM_[bFm->getPanNumber()]) return false; return true; } //----- SSG methods ----- void InstrumentsManager::setInstrumentSSGWaveformEnabled(int instNum, bool enabled) { auto ssg = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); ssg->setWaveformEnabled(enabled); if (enabled) wfSSG_.at(static_cast(ssg->getWaveformNumber()))->registerUserInstrument(instNum); else wfSSG_.at(static_cast(ssg->getWaveformNumber()))->deregisterUserInstrument(instNum); } bool InstrumentsManager::getInstrumentSSGWaveformEnabled(int instNum) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getWaveformEnabled(); } void InstrumentsManager::setInstrumentSSGWaveform(int instNum, int wfNum) { auto ssg = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (ssg->getWaveformEnabled()) { wfSSG_.at(static_cast(ssg->getWaveformNumber()))->deregisterUserInstrument(instNum); wfSSG_.at(static_cast(wfNum))->registerUserInstrument(instNum); } ssg->setWaveformNumber(wfNum); } int InstrumentsManager::getInstrumentSSGWaveform(int instNum) { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getWaveformNumber(); } void InstrumentsManager::addWaveformSSGSequenceData(int wfNum, const SSGWaveformUnit& data) { wfSSG_.at(static_cast(wfNum))->addSequenceUnit(data); } void InstrumentsManager::removeWaveformSSGSequenceData(int wfNum) { wfSSG_.at(static_cast(wfNum))->removeSequenceUnit(); } void InstrumentsManager::setWaveformSSGSequenceData(int wfNum, int cnt, const SSGWaveformUnit& data) { wfSSG_.at(static_cast(wfNum))->setSequenceUnit(cnt, data); } std::vector InstrumentsManager::getWaveformSSGSequence(int wfNum) { return wfSSG_.at(static_cast(wfNum))->getSequence(); } void InstrumentsManager::addWaveformSSGLoop(int wfNum, const InstrumentSequenceLoop& loop) { wfSSG_.at(static_cast(wfNum))->addLoop(loop); } void InstrumentsManager::removeWaveformSSGLoop(int wfNum, int begin, int end) { wfSSG_.at(static_cast(wfNum))->removeLoop(begin, end); } void InstrumentsManager::changeWaveformSSGLoop(int wfNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { wfSSG_.at(static_cast(wfNum))->changeLoop(prevBegin, prevEnd, loop); } void InstrumentsManager::clearWaveformSSGLoops(int wfNum) { wfSSG_.at(static_cast(wfNum))->clearLoops(); } InstrumentSequenceLoopRoot InstrumentsManager::getWaveformSSGLoopRoot(int wfNum) const { return wfSSG_.at(static_cast(wfNum))->getLoopRoot(); } void InstrumentsManager::setWaveformSSGRelease(int wfNum, const InstrumentSequenceRelease& release) { wfSSG_.at(static_cast(wfNum))->setRelease(release); } InstrumentSequenceRelease InstrumentsManager::getWaveformSSGRelease(int wfNum) const { return wfSSG_.at(static_cast(wfNum))->getRelease(); } SSGWaveformIter InstrumentsManager::getWaveformSSGIterator(int wfNum) const { return wfSSG_.at(static_cast(wfNum))->getIterator(); } std::multiset InstrumentsManager::getWaveformSSGUsers(int wfNum) const { return wfSSG_.at(static_cast(wfNum))->getUserInstruments(); } std::vector InstrumentsManager::getWaveformSSGEntriedIndices() const { return utils::findIndicesIf(wfSSG_, IsUsedOrEdited()); } int InstrumentsManager::findFirstAssignableWaveformSSG() const { return findFirstAssignableProperty(wfSSG_, regardingUnedited_); } void InstrumentsManager::setInstrumentSSGToneNoiseEnabled(int instNum, bool enabled) { auto ssg = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); ssg->setToneNoiseEnabled(enabled); if (enabled) tnSSG_.at(static_cast(ssg->getToneNoiseNumber()))->registerUserInstrument(instNum); else tnSSG_.at(static_cast(ssg->getToneNoiseNumber()))->deregisterUserInstrument(instNum); } bool InstrumentsManager::getInstrumentSSGToneNoiseEnabled(int instNum) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getToneNoiseEnabled(); } void InstrumentsManager::setInstrumentSSGToneNoise(int instNum, int tnNum) { auto ssg = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (ssg->getToneNoiseEnabled()) { tnSSG_.at(static_cast(ssg->getToneNoiseNumber()))->deregisterUserInstrument(instNum); tnSSG_.at(static_cast(tnNum))->registerUserInstrument(instNum); } ssg->setToneNoiseNumber(tnNum); } int InstrumentsManager::getInstrumentSSGToneNoise(int instNum) { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getToneNoiseNumber(); } void InstrumentsManager::addToneNoiseSSGSequenceData(int tnNum, int data) { tnSSG_.at(static_cast(tnNum))->addSequenceUnit(SSGToneNoiseUnit(data)); } void InstrumentsManager::removeToneNoiseSSGSequenceData(int tnNum) { tnSSG_.at(static_cast(tnNum))->removeSequenceUnit(); } void InstrumentsManager::setToneNoiseSSGSequenceData(int tnNum, int cnt, int data) { tnSSG_.at(static_cast(tnNum))->setSequenceUnit(cnt, SSGToneNoiseUnit(data)); } std::vector InstrumentsManager::getToneNoiseSSGSequence(int tnNum) { return tnSSG_.at(static_cast(tnNum))->getSequence(); } void InstrumentsManager::addToneNoiseSSGLoop(int tnNum, const InstrumentSequenceLoop& loop) { tnSSG_.at(static_cast(tnNum))->addLoop(loop); } void InstrumentsManager::removeToneNoiseSSGLoop(int tnNum, int begin, int end) { tnSSG_.at(static_cast(tnNum))->removeLoop(begin, end); } void InstrumentsManager::changeToneNoiseSSGLoop(int tnNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { tnSSG_.at(static_cast(tnNum))->changeLoop(prevBegin, prevEnd, loop); } void InstrumentsManager::clearToneNoiseSSGLoops(int tnNum) { tnSSG_.at(static_cast(tnNum))->clearLoops(); } InstrumentSequenceLoopRoot InstrumentsManager::getToneNoiseSSGLoopRoot(int tnNum) const { return tnSSG_.at(static_cast(tnNum))->getLoopRoot(); } void InstrumentsManager::setToneNoiseSSGRelease(int tnNum, const InstrumentSequenceRelease& release) { tnSSG_.at(static_cast(tnNum))->setRelease(release); } InstrumentSequenceRelease InstrumentsManager::getToneNoiseSSGRelease(int tnNum) const { return tnSSG_.at(static_cast(tnNum))->getRelease(); } SSGToneNoiseIter InstrumentsManager::getToneNoiseSSGIterator(int tnNum) const { return tnSSG_.at(static_cast(tnNum))->getIterator(); } std::multiset InstrumentsManager::getToneNoiseSSGUsers(int tnNum) const { return tnSSG_.at(static_cast(tnNum))->getUserInstruments(); } std::vector InstrumentsManager::getToneNoiseSSGEntriedIndices() const { return utils::findIndicesIf(tnSSG_, IsUsedOrEdited()); } int InstrumentsManager::findFirstAssignableToneNoiseSSG() const { return findFirstAssignableProperty(tnSSG_, regardingUnedited_); } void InstrumentsManager::setInstrumentSSGEnvelopeEnabled(int instNum, bool enabled) { auto ssg = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); ssg->setEnvelopeEnabled(enabled); if (enabled) envSSG_.at(static_cast(ssg->getEnvelopeNumber()))->registerUserInstrument(instNum); else envSSG_.at(static_cast(ssg->getEnvelopeNumber()))->deregisterUserInstrument(instNum); } bool InstrumentsManager::getInstrumentSSGEnvelopeEnabled(int instNum) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getEnvelopeEnabled(); } void InstrumentsManager::setInstrumentSSGEnvelope(int instNum, int envNum) { auto ssg = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (ssg->getEnvelopeEnabled()) { envSSG_.at(static_cast(ssg->getEnvelopeNumber()))->deregisterUserInstrument(instNum); envSSG_.at(static_cast(envNum))->registerUserInstrument(instNum); } ssg->setEnvelopeNumber(envNum); } int InstrumentsManager::getInstrumentSSGEnvelope(int instNum) { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getEnvelopeNumber(); } void InstrumentsManager::addEnvelopeSSGSequenceData(int envNum, const SSGEnvelopeUnit& data) { envSSG_.at(static_cast(envNum))->addSequenceUnit(data); } void InstrumentsManager::removeEnvelopeSSGSequenceData(int envNum) { envSSG_.at(static_cast(envNum))->removeSequenceUnit(); } void InstrumentsManager::setEnvelopeSSGSequenceData(int envNum, int cnt, const SSGEnvelopeUnit& data) { envSSG_.at(static_cast(envNum))->setSequenceUnit(cnt, data); } std::vector InstrumentsManager::getEnvelopeSSGSequence(int envNum) { return envSSG_.at(static_cast(envNum))->getSequence(); } void InstrumentsManager::addEnvelopeSSGLoop(int envNum, const InstrumentSequenceLoop& loop) { envSSG_.at(static_cast(envNum))->addLoop(loop); } void InstrumentsManager::removeEnvelopeSSGLoop(int envNum, int begin, int end) { envSSG_.at(static_cast(envNum))->removeLoop(begin, end); } void InstrumentsManager::changeEnvelopeSSGLoop(int envNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { envSSG_.at(static_cast(envNum))->changeLoop(prevBegin, prevEnd, loop); } void InstrumentsManager::clearEnvelopeSSGLoops(int envNum) { envSSG_.at(static_cast(envNum))->clearLoops(); } InstrumentSequenceLoopRoot InstrumentsManager::getEnvelopeSSGLoopRoot(int envNum) const { return envSSG_.at(static_cast(envNum))->getLoopRoot(); } void InstrumentsManager::setEnvelopeSSGRelease(int envNum, const InstrumentSequenceRelease& release) { envSSG_.at(static_cast(envNum))->setRelease(release); } InstrumentSequenceRelease InstrumentsManager::getEnvelopeSSGRelease(int envNum) const { return envSSG_.at(static_cast(envNum))->getRelease(); } SSGEnvelopeIter InstrumentsManager::getEnvelopeSSGIterator(int envNum) const { return envSSG_.at(static_cast(envNum))->getIterator(); } std::multiset InstrumentsManager::getEnvelopeSSGUsers(int envNum) const { return envSSG_.at(static_cast(envNum))->getUserInstruments(); } std::vector InstrumentsManager::getEnvelopeSSGEntriedIndices() const { return utils::findIndicesIf(envSSG_, IsUsedOrEdited()); } int InstrumentsManager::findFirstAssignableEnvelopeSSG() const { return findFirstAssignableProperty(envSSG_, regardingUnedited_); } void InstrumentsManager::setInstrumentSSGArpeggioEnabled(int instNum, bool enabled) { auto ssg = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); ssg->setArpeggioEnabled(enabled); if (enabled) arpSSG_.at(static_cast(ssg->getArpeggioNumber()))->registerUserInstrument(instNum); else arpSSG_.at(static_cast(ssg->getArpeggioNumber()))->deregisterUserInstrument(instNum); } bool InstrumentsManager::getInstrumentSSGArpeggioEnabled(int instNum) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getArpeggioEnabled(); } void InstrumentsManager::setInstrumentSSGArpeggio(int instNum, int arpNum) { auto ssg = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (ssg->getArpeggioEnabled()) { arpSSG_.at(static_cast(ssg->getArpeggioNumber()))->deregisterUserInstrument(instNum); arpSSG_.at(static_cast(arpNum))->registerUserInstrument(instNum); } ssg->setArpeggioNumber(arpNum); } int InstrumentsManager::getInstrumentSSGArpeggio(int instNum) { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getArpeggioNumber(); } void InstrumentsManager::setArpeggioSSGType(int arpNum, SequenceType type) { arpSSG_.at(static_cast(arpNum))->setType(type); } SequenceType InstrumentsManager::getArpeggioSSGType(int arpNum) const { return arpSSG_.at(static_cast(arpNum))->getType(); } void InstrumentsManager::addArpeggioSSGSequenceData(int arpNum, int data) { arpSSG_.at(static_cast(arpNum))->addSequenceUnit(ArpeggioUnit(data)); } void InstrumentsManager::removeArpeggioSSGSequenceData(int arpNum) { arpSSG_.at(static_cast(arpNum))->removeSequenceUnit(); } void InstrumentsManager::setArpeggioSSGSequenceData(int arpNum, int cnt, int data) { arpSSG_.at(static_cast(arpNum))->setSequenceUnit(cnt, ArpeggioUnit(data)); } std::vector InstrumentsManager::getArpeggioSSGSequence(int arpNum) { return arpSSG_.at(static_cast(arpNum))->getSequence(); } void InstrumentsManager::addArpeggioSSGLoop(int arpNum, const InstrumentSequenceLoop& loop) { arpSSG_.at(static_cast(arpNum))->addLoop(loop); } void InstrumentsManager::removeArpeggioSSGLoop(int arpNum, int begin, int end) { arpSSG_.at(static_cast(arpNum))->removeLoop(begin, end); } void InstrumentsManager::changeArpeggioSSGLoop(int arpNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { arpSSG_.at(static_cast(arpNum))->changeLoop(prevBegin, prevEnd, loop); } void InstrumentsManager::clearArpeggioSSGLoops(int arpNum) { arpSSG_.at(static_cast(arpNum))->clearLoops(); } InstrumentSequenceLoopRoot InstrumentsManager::getArpeggioSSGLoopRoot(int arpNum) const { return arpSSG_.at(static_cast(arpNum))->getLoopRoot(); } void InstrumentsManager::setArpeggioSSGRelease(int arpNum, const InstrumentSequenceRelease& release) { arpSSG_.at(static_cast(arpNum))->setRelease(release); } InstrumentSequenceRelease InstrumentsManager::getArpeggioSSGRelease(int arpNum) const { return arpSSG_.at(static_cast(arpNum))->getRelease(); } ArpeggioIter InstrumentsManager::getArpeggioSSGIterator(int arpNum) const { return arpSSG_.at(static_cast(arpNum))->getIterator(); } std::multiset InstrumentsManager::getArpeggioSSGUsers(int arpNum) const { return arpSSG_.at(static_cast(arpNum))->getUserInstruments(); } std::vector InstrumentsManager::getArpeggioSSGEntriedIndices() const { return utils::findIndicesIf(arpSSG_, IsUsedOrEdited()); } int InstrumentsManager::findFirstAssignableArpeggioSSG() const { return findFirstAssignableProperty(arpSSG_, regardingUnedited_); } void InstrumentsManager::setInstrumentSSGPitchEnabled(int instNum, bool enabled) { auto ssg = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); ssg->setPitchEnabled(enabled); if (enabled) ptSSG_.at(static_cast(ssg->getPitchNumber()))->registerUserInstrument(instNum); else ptSSG_.at(static_cast(ssg->getPitchNumber()))->deregisterUserInstrument(instNum); } bool InstrumentsManager::getInstrumentSSGPitchEnabled(int instNum) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getPitchEnabled(); } void InstrumentsManager::setInstrumentSSGPitch(int instNum, int ptNum) { auto ssg = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (ssg->getPitchEnabled()) { ptSSG_.at(static_cast(ssg->getPitchNumber()))->deregisterUserInstrument(instNum); ptSSG_.at(static_cast(ptNum))->registerUserInstrument(instNum); } ssg->setPitchNumber(ptNum); } int InstrumentsManager::getInstrumentSSGPitch(int instNum) { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getPitchNumber(); } void InstrumentsManager::setPitchSSGType(int ptNum, SequenceType type) { ptSSG_.at(static_cast(ptNum))->setType(type); } SequenceType InstrumentsManager::getPitchSSGType(int ptNum) const { return ptSSG_.at(static_cast(ptNum))->getType(); } void InstrumentsManager::addPitchSSGSequenceData(int ptNum, int data) { ptSSG_.at(static_cast(ptNum))->addSequenceUnit(PitchUnit(data)); } void InstrumentsManager::removePitchSSGSequenceData(int ptNum) { ptSSG_.at(static_cast(ptNum))->removeSequenceUnit(); } void InstrumentsManager::setPitchSSGSequenceData(int ptNum, int cnt, int data) { ptSSG_.at(static_cast(ptNum))->setSequenceUnit(cnt, PitchUnit(data)); } std::vector InstrumentsManager::getPitchSSGSequence(int ptNum) { return ptSSG_.at(static_cast(ptNum))->getSequence(); } void InstrumentsManager::addPitchSSGLoop(int ptNum, const InstrumentSequenceLoop& loop) { ptSSG_.at(static_cast(ptNum))->addLoop(loop); } void InstrumentsManager::removePitchSSGLoop(int ptNum, int begin, int end) { ptSSG_.at(static_cast(ptNum))->removeLoop(begin, end); } void InstrumentsManager::changePitchSSGLoop(int ptNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { ptSSG_.at(static_cast(ptNum))->changeLoop(prevBegin, prevEnd, loop); } void InstrumentsManager::clearPitchSSGLoops(int ptNum) { ptSSG_.at(static_cast(ptNum))->clearLoops(); } InstrumentSequenceLoopRoot InstrumentsManager::getPitchSSGLoopRoot(int ptNum) const { return ptSSG_.at(static_cast(ptNum))->getLoopRoot(); } void InstrumentsManager::setPitchSSGRelease(int ptNum, const InstrumentSequenceRelease& release) { ptSSG_.at(static_cast(ptNum))->setRelease(release); } InstrumentSequenceRelease InstrumentsManager::getPitchSSGRelease(int ptNum) const { return ptSSG_.at(static_cast(ptNum))->getRelease(); } PitchIter InstrumentsManager::getPitchSSGIterator(int ptNum) const { return ptSSG_.at(static_cast(ptNum))->getIterator(); } std::multiset InstrumentsManager::getPitchSSGUsers(int ptNum) const { return ptSSG_.at(static_cast(ptNum))->getUserInstruments(); } std::vector InstrumentsManager::getPitchSSGEntriedIndices() const { return utils::findIndicesIf(ptSSG_, IsUsedOrEdited()); } int InstrumentsManager::findFirstAssignablePitchSSG() const { return findFirstAssignableProperty(ptSSG_, regardingUnedited_); } bool InstrumentsManager::equalPropertiesSSG(std::shared_ptr a, std::shared_ptr b) const { auto aSsg = std::dynamic_pointer_cast(a); auto bSsg = std::dynamic_pointer_cast(b); if (aSsg->getWaveformEnabled() != bSsg->getWaveformEnabled()) return false; if (aSsg->getWaveformEnabled() && *wfSSG_[aSsg->getWaveformNumber()] != *wfSSG_[bSsg->getWaveformNumber()]) return false; if (aSsg->getToneNoiseEnabled() != bSsg->getToneNoiseEnabled()) return false; if (aSsg->getToneNoiseEnabled() && *tnSSG_[aSsg->getToneNoiseNumber()] != *tnSSG_[bSsg->getToneNoiseNumber()]) return false; if (aSsg->getEnvelopeEnabled() != bSsg->getEnvelopeEnabled()) return false; if (aSsg->getEnvelopeEnabled() && *envSSG_[aSsg->getEnvelopeNumber()] != *envSSG_[bSsg->getEnvelopeNumber()]) return false; if (aSsg->getArpeggioEnabled() != bSsg->getArpeggioEnabled()) return false; if (aSsg->getArpeggioEnabled() && *arpSSG_[aSsg->getArpeggioNumber()] != *arpSSG_[bSsg->getArpeggioNumber()]) return false; if (aSsg->getPitchEnabled() != bSsg->getPitchEnabled()) return false; if (aSsg->getPitchEnabled() && *ptSSG_[aSsg->getPitchNumber()] != *ptSSG_[bSsg->getPitchNumber()]) return false; return true; } //----- ADPCM methods ----- void InstrumentsManager::setInstrumentADPCMSample(int instNum, int sampNum) { auto adpcm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); sampADPCM_.at(static_cast(adpcm->getSampleNumber()))->deregisterUserInstrument(instNum); sampADPCM_.at(static_cast(sampNum))->registerUserInstrument(instNum); adpcm->setSampleNumber(sampNum); } int InstrumentsManager::getInstrumentADPCMSample(int instNum) { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getSampleNumber(); } void InstrumentsManager::setSampleADPCMRootKeyNumber(int sampNum, int n) { sampADPCM_.at(static_cast(sampNum))->setRootKeyNumber(n); } int InstrumentsManager::getSampleADPCMRootKeyNumber(int sampNum) const { return sampADPCM_.at(static_cast(sampNum))->getRootKeyNumber(); } void InstrumentsManager::setSampleADPCMRootDeltaN(int sampNum, int dn) { sampADPCM_.at(static_cast(sampNum))->setRootDeltaN(dn); } int InstrumentsManager::getSampleADPCMRootDeltaN(int sampNum) const { return sampADPCM_.at(static_cast(sampNum))->getRootDeltaN(); } void InstrumentsManager::setSampleADPCMRepeatEnabled(int sampNum, bool enabled) { sampADPCM_.at(static_cast(sampNum))->setRepeatEnabled(enabled); } bool InstrumentsManager::isSampleADPCMRepeatable(int sampNum) const { return sampADPCM_.at(static_cast(sampNum))->isRepeatable(); } bool InstrumentsManager::setSampleADPCMRepeatrange(int sampNum, const SampleRepeatRange& range) { return sampADPCM_.at(static_cast(sampNum))->setRepeatRange(range); } SampleRepeatFlag InstrumentsManager::getSampleADPCMRepeatFlag(int sampNum) const { return sampADPCM_.at(static_cast(sampNum))->getRepeatFlag(); } SampleRepeatRange InstrumentsManager::getSampleADPCMRepeatRange(int sampNum) const { return sampADPCM_.at(static_cast(sampNum))->getRepeatRange(); } void InstrumentsManager::storeSampleADPCMRawSample(int sampNum, const std::vector& sample) { sampADPCM_.at(static_cast(sampNum))->storeSample(sample); } void InstrumentsManager::storeSampleADPCMRawSample(int sampNum, std::vector&& sample) { sampADPCM_.at(static_cast(sampNum))->storeSample(sample); } void InstrumentsManager::clearSampleADPCMRawSample(int sampNum) { sampADPCM_.at(static_cast(sampNum))->clearSample(); } std::vector InstrumentsManager::getSampleADPCMRawSample(int sampNum) const { return sampADPCM_.at(static_cast(sampNum))->getSamples(); } void InstrumentsManager::setSampleADPCMStartAddress(int sampNum, size_t addr) { sampADPCM_.at(static_cast(sampNum))->setStartAddress(addr); } size_t InstrumentsManager::getSampleADPCMStartAddress(int sampNum) const { return sampADPCM_.at(static_cast(sampNum))->getStartAddress(); } void InstrumentsManager::setSampleADPCMStopAddress(int sampNum, size_t addr) { sampADPCM_.at(static_cast(sampNum))->setStopAddress(addr); } size_t InstrumentsManager::getSampleADPCMStopAddress(int sampNum) const { return sampADPCM_.at(static_cast(sampNum))->getStopAddress(); } std::multiset InstrumentsManager::getSampleADPCMUsers(int sampNum) const { return sampADPCM_.at(static_cast(sampNum))->getUserInstruments(); } std::vector InstrumentsManager::getSampleADPCMEntriedIndices() const { return utils::findIndicesIf(sampADPCM_, IsEdited()); } std::vector InstrumentsManager::getSampleADPCMValidIndices() const { return utils::findIndicesIf(sampADPCM_, IsUsedOrEdited()); } void InstrumentsManager::clearUnusedSamplesADPCM() { for (size_t i = 0; i < 128; ++i) { if (!sampADPCM_[i]->isUserInstrument()) sampADPCM_[i] = std::make_shared(i); } } int InstrumentsManager::findFirstAssignableSampleADPCM(int startIndex) const { return findFirstAssignableProperty(sampADPCM_, regardingUnedited_, startIndex); } void InstrumentsManager::setInstrumentADPCMEnvelopeEnabled(int instNum, bool enabled) { auto adpcm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); adpcm->setEnvelopeEnabled(enabled); if (enabled) envADPCM_.at(static_cast(adpcm->getEnvelopeNumber()))->registerUserInstrument(instNum); else envADPCM_.at(static_cast(adpcm->getEnvelopeNumber()))->deregisterUserInstrument(instNum); } bool InstrumentsManager::getInstrumentADPCMEnvelopeEnabled(int instNum) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getEnvelopeEnabled(); } void InstrumentsManager::setInstrumentADPCMEnvelope(int instNum, int envNum) { auto adpcm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (adpcm->getEnvelopeEnabled()) { envADPCM_.at(static_cast(adpcm->getEnvelopeNumber()))->deregisterUserInstrument(instNum); envADPCM_.at(static_cast(envNum))->registerUserInstrument(instNum); } adpcm->setEnvelopeNumber(envNum); } int InstrumentsManager::getInstrumentADPCMEnvelope(int instNum) { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getEnvelopeNumber(); } void InstrumentsManager::addEnvelopeADPCMSequenceData(int envNum, int data) { envADPCM_.at(static_cast(envNum))->addSequenceUnit(ADPCMEnvelopeUnit(data)); } void InstrumentsManager::removeEnvelopeADPCMSequenceData(int envNum) { envADPCM_.at(static_cast(envNum))->removeSequenceUnit(); } void InstrumentsManager::setEnvelopeADPCMSequenceData(int envNum, int cnt, int data) { envADPCM_.at(static_cast(envNum))->setSequenceUnit(cnt, ADPCMEnvelopeUnit(data)); } std::vector InstrumentsManager::getEnvelopeADPCMSequence(int envNum) { return envADPCM_.at(static_cast(envNum))->getSequence(); } void InstrumentsManager::addEnvelopeADPCMLoop(int envNum, const InstrumentSequenceLoop& loop) { envADPCM_.at(static_cast(envNum))->addLoop(loop); } void InstrumentsManager::removeEnvelopeADPCMLoop(int envNum, int begin, int end) { envADPCM_.at(static_cast(envNum))->removeLoop(begin, end); } void InstrumentsManager::changeEnvelopeADPCMLoop(int envNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { envADPCM_.at(static_cast(envNum))->changeLoop(prevBegin, prevEnd, loop); } void InstrumentsManager::clearEnvelopeADPCMLoops(int envNum) { envADPCM_.at(static_cast(envNum))->clearLoops(); } InstrumentSequenceLoopRoot InstrumentsManager::getEnvelopeADPCMLoopRoot(int envNum) const { return envADPCM_.at(static_cast(envNum))->getLoopRoot(); } void InstrumentsManager::setEnvelopeADPCMRelease(int envNum, const InstrumentSequenceRelease& release) { envADPCM_.at(static_cast(envNum))->setRelease(release); } InstrumentSequenceRelease InstrumentsManager::getEnvelopeADPCMRelease(int envNum) const { return envADPCM_.at(static_cast(envNum))->getRelease(); } ADPCMEnvelopeIter InstrumentsManager::getEnvelopeADPCMIterator(int envNum) const { return envADPCM_.at(static_cast(envNum))->getIterator(); } std::multiset InstrumentsManager::getEnvelopeADPCMUsers(int envNum) const { return envADPCM_.at(static_cast(envNum))->getUserInstruments(); } std::vector InstrumentsManager::getEnvelopeADPCMEntriedIndices() const { return utils::findIndicesIf(envADPCM_, IsUsedOrEdited()); } int InstrumentsManager::findFirstAssignableEnvelopeADPCM() const { return findFirstAssignableProperty(envADPCM_, regardingUnedited_); } void InstrumentsManager::setInstrumentADPCMArpeggioEnabled(int instNum, bool enabled) { auto adpcm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); adpcm->setArpeggioEnabled(enabled); if (enabled) arpADPCM_.at(static_cast(adpcm->getArpeggioNumber()))->registerUserInstrument(instNum); else arpADPCM_.at(static_cast(adpcm->getArpeggioNumber()))->deregisterUserInstrument(instNum); } bool InstrumentsManager::getInstrumentADPCMArpeggioEnabled(int instNum) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getArpeggioEnabled(); } void InstrumentsManager::setInstrumentADPCMArpeggio(int instNum, int arpNum) { auto adpcm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (adpcm->getArpeggioEnabled()) { arpADPCM_.at(static_cast(adpcm->getArpeggioNumber()))->deregisterUserInstrument(instNum); arpADPCM_.at(static_cast(arpNum))->registerUserInstrument(instNum); } adpcm->setArpeggioNumber(arpNum); } int InstrumentsManager::getInstrumentADPCMArpeggio(int instNum) { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getArpeggioNumber(); } void InstrumentsManager::setArpeggioADPCMType(int arpNum, SequenceType type) { arpADPCM_.at(static_cast(arpNum))->setType(type); } SequenceType InstrumentsManager::getArpeggioADPCMType(int arpNum) const { return arpADPCM_.at(static_cast(arpNum))->getType(); } void InstrumentsManager::addArpeggioADPCMSequenceData(int arpNum, int data) { arpADPCM_.at(static_cast(arpNum))->addSequenceUnit(ArpeggioUnit(data)); } void InstrumentsManager::removeArpeggioADPCMSequenceData(int arpNum) { arpADPCM_.at(static_cast(arpNum))->removeSequenceUnit(); } void InstrumentsManager::setArpeggioADPCMSequenceData(int arpNum, int cnt, int data) { arpADPCM_.at(static_cast(arpNum))->setSequenceUnit(cnt, ArpeggioUnit(data)); } std::vector InstrumentsManager::getArpeggioADPCMSequence(int arpNum) { return arpADPCM_.at(static_cast(arpNum))->getSequence(); } void InstrumentsManager::addArpeggioADPCMLoop(int arpNum, const InstrumentSequenceLoop& loop) { arpADPCM_.at(static_cast(arpNum))->addLoop(loop); } void InstrumentsManager::removeArpeggioADPCMLoop(int arpNum, int begin, int end) { arpADPCM_.at(static_cast(arpNum))->removeLoop(begin, end); } void InstrumentsManager::changeArpeggioADPCMLoop(int arpNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { arpADPCM_.at(static_cast(arpNum))->changeLoop(prevBegin, prevEnd, loop); } void InstrumentsManager::clearArpeggioADPCMLoops(int arpNum) { arpADPCM_.at(static_cast(arpNum))->clearLoops(); } InstrumentSequenceLoopRoot InstrumentsManager::getArpeggioADPCMLoopRoot(int arpNum) const { return arpADPCM_.at(static_cast(arpNum))->getLoopRoot(); } void InstrumentsManager::setArpeggioADPCMRelease(int arpNum, const InstrumentSequenceRelease& release) { arpADPCM_.at(static_cast(arpNum))->setRelease(release); } InstrumentSequenceRelease InstrumentsManager::getArpeggioADPCMRelease(int arpNum) const { return arpADPCM_.at(static_cast(arpNum))->getRelease(); } ArpeggioIter InstrumentsManager::getArpeggioADPCMIterator(int arpNum) const { return arpADPCM_.at(static_cast(arpNum))->getIterator(); } std::multiset InstrumentsManager::getArpeggioADPCMUsers(int arpNum) const { return arpADPCM_.at(static_cast(arpNum))->getUserInstruments(); } std::vector InstrumentsManager::getArpeggioADPCMEntriedIndices() const { return utils::findIndicesIf(arpADPCM_, IsUsedOrEdited()); } int InstrumentsManager::findFirstAssignableArpeggioADPCM() const { return findFirstAssignableProperty(arpADPCM_, regardingUnedited_); } void InstrumentsManager::setInstrumentADPCMPitchEnabled(int instNum, bool enabled) { auto adpcm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); adpcm->setPitchEnabled(enabled); if (enabled) ptADPCM_.at(static_cast(adpcm->getPitchNumber()))->registerUserInstrument(instNum); else ptADPCM_.at(static_cast(adpcm->getPitchNumber()))->deregisterUserInstrument(instNum); } bool InstrumentsManager::getInstrumentADPCMPitchEnabled(int instNum) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getPitchEnabled(); } void InstrumentsManager::setInstrumentADPCMPitch(int instNum, int ptNum) { auto adpcm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (adpcm->getPitchEnabled()) { ptADPCM_.at(static_cast(adpcm->getPitchNumber()))->deregisterUserInstrument(instNum); ptADPCM_.at(static_cast(ptNum))->registerUserInstrument(instNum); } adpcm->setPitchNumber(ptNum); } int InstrumentsManager::getInstrumentADPCMPitch(int instNum) { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getPitchNumber(); } void InstrumentsManager::setPitchADPCMType(int ptNum, SequenceType type) { ptADPCM_.at(static_cast(ptNum))->setType(type); } SequenceType InstrumentsManager::getPitchADPCMType(int ptNum) const { return ptADPCM_.at(static_cast(ptNum))->getType(); } void InstrumentsManager::addPitchADPCMSequenceData(int ptNum, int data) { ptADPCM_.at(static_cast(ptNum))->addSequenceUnit(PitchUnit(data)); } void InstrumentsManager::removePitchADPCMSequenceData(int ptNum) { ptADPCM_.at(static_cast(ptNum))->removeSequenceUnit(); } void InstrumentsManager::setPitchADPCMSequenceData(int ptNum, int cnt, int data) { ptADPCM_.at(static_cast(ptNum))->setSequenceUnit(cnt, PitchUnit(data)); } std::vector InstrumentsManager::getPitchADPCMSequence(int ptNum) { return ptADPCM_.at(static_cast(ptNum))->getSequence(); } void InstrumentsManager::addPitchADPCMLoop(int ptNum, const InstrumentSequenceLoop& loop) { ptADPCM_.at(static_cast(ptNum))->addLoop(loop); } void InstrumentsManager::removePitchADPCMLoop(int ptNum, int begin, int end) { ptADPCM_.at(static_cast(ptNum))->removeLoop(begin, end); } void InstrumentsManager::changePitchADPCMLoop(int ptNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { ptADPCM_.at(static_cast(ptNum))->changeLoop(prevBegin, prevEnd, loop); } void InstrumentsManager::clearPitchADPCMLoops(int ptNum) { ptADPCM_.at(static_cast(ptNum))->clearLoops(); } InstrumentSequenceLoopRoot InstrumentsManager::getPitchADPCMLoopRoot(int ptNum) const { return ptADPCM_.at(static_cast(ptNum))->getLoopRoot(); } void InstrumentsManager::setPitchADPCMRelease(int ptNum, const InstrumentSequenceRelease& release) { ptADPCM_.at(static_cast(ptNum))->setRelease(release); } InstrumentSequenceRelease InstrumentsManager::getPitchADPCMRelease(int ptNum) const { return ptADPCM_.at(static_cast(ptNum))->getRelease(); } PitchIter InstrumentsManager::getPitchADPCMIterator(int ptNum) const { return ptADPCM_.at(static_cast(ptNum))->getIterator(); } std::multiset InstrumentsManager::getPitchADPCMUsers(int ptNum) const { return ptADPCM_.at(static_cast(ptNum))->getUserInstruments(); } std::vector InstrumentsManager::getPitchADPCMEntriedIndices() const { return utils::findIndicesIf(ptADPCM_, IsUsedOrEdited()); } int InstrumentsManager::findFirstAssignablePitchADPCM() const { return findFirstAssignableProperty(ptADPCM_, regardingUnedited_); } void InstrumentsManager::setInstrumentADPCMPanEnabled(int instNum, bool enabled) { auto adpcm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); adpcm->setPanEnabled(enabled); if (enabled) panADPCM_.at(static_cast(adpcm->getPanNumber()))->registerUserInstrument(instNum); else panADPCM_.at(static_cast(adpcm->getPanNumber()))->deregisterUserInstrument(instNum); } bool InstrumentsManager::getInstrumentADPCMPanEnabled(int instNum) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getPanEnabled(); } void InstrumentsManager::setInstrumentADPCMPan(int instNum, int panNum) { auto adpcm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (adpcm->getPanEnabled()) { panADPCM_.at(static_cast(adpcm->getPanNumber()))->deregisterUserInstrument(instNum); panADPCM_.at(static_cast(panNum))->registerUserInstrument(instNum); } adpcm->setPanNumber(panNum); } int InstrumentsManager::getInstrumentADPCMPan(int instNum) { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getPanNumber(); } void InstrumentsManager::addPanADPCMSequenceData(int panNum, int data) { panADPCM_.at(static_cast(panNum))->addSequenceUnit(PanUnit(data)); } void InstrumentsManager::removePanADPCMSequenceData(int panNum) { panADPCM_.at(static_cast(panNum))->removeSequenceUnit(); } void InstrumentsManager::setPanADPCMSequenceData(int panNum, int cnt, int data) { panADPCM_.at(static_cast(panNum))->setSequenceUnit(cnt, PanUnit(data)); } std::vector InstrumentsManager::getPanADPCMSequence(int panNum) { return panADPCM_.at(static_cast(panNum))->getSequence(); } void InstrumentsManager::addPanADPCMLoop(int panNum, const InstrumentSequenceLoop& loop) { panADPCM_.at(static_cast(panNum))->addLoop(loop); } void InstrumentsManager::removePanADPCMLoop(int panNum, int begin, int end) { panADPCM_.at(static_cast(panNum))->removeLoop(begin, end); } void InstrumentsManager::changePanADPCMLoop(int panNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { panADPCM_.at(static_cast(panNum))->changeLoop(prevBegin, prevEnd, loop); } void InstrumentsManager::clearPanADPCMLoops(int panNum) { panADPCM_.at(static_cast(panNum))->clearLoops(); } InstrumentSequenceLoopRoot InstrumentsManager::getPanADPCMLoopRoot(int panNum) const { return panADPCM_.at(static_cast(panNum))->getLoopRoot(); } void InstrumentsManager::setPanADPCMRelease(int panNum, const InstrumentSequenceRelease& release) { panADPCM_.at(static_cast(panNum))->setRelease(release); } InstrumentSequenceRelease InstrumentsManager::getPanADPCMRelease(int panNum) const { return panADPCM_.at(static_cast(panNum))->getRelease(); } PanIter InstrumentsManager::getPanADPCMIterator(int panNum) const { return panADPCM_.at(static_cast(panNum))->getIterator(); } std::multiset InstrumentsManager::getPanADPCMUsers(int panNum) const { return panADPCM_.at(static_cast(panNum))->getUserInstruments(); } std::vector InstrumentsManager::getPanADPCMEntriedIndices() const { return utils::findIndicesIf(panADPCM_, IsUsedOrEdited()); } int InstrumentsManager::findFirstAssignablePanADPCM() const { return findFirstAssignableProperty(panADPCM_, regardingUnedited_); } bool InstrumentsManager::equalPropertiesADPCM(std::shared_ptr a, std::shared_ptr b) const { auto aAdpcm = std::dynamic_pointer_cast(a); auto bAdpcm = std::dynamic_pointer_cast(b); if (*sampADPCM_[aAdpcm->getSampleNumber()] != *sampADPCM_[bAdpcm->getSampleNumber()]) return false; if (aAdpcm->getEnvelopeEnabled() != bAdpcm->getEnvelopeEnabled()) return false; if (aAdpcm->getEnvelopeEnabled() && *envADPCM_[aAdpcm->getEnvelopeNumber()] != *envADPCM_[bAdpcm->getEnvelopeNumber()]) return false; if (aAdpcm->getArpeggioEnabled() != bAdpcm->getArpeggioEnabled()) return false; if (aAdpcm->getArpeggioEnabled() && *arpADPCM_[aAdpcm->getArpeggioNumber()] != *arpADPCM_[bAdpcm->getArpeggioNumber()]) return false; if (aAdpcm->getPitchEnabled() != bAdpcm->getPitchEnabled()) return false; if (aAdpcm->getPitchEnabled() && *ptADPCM_[aAdpcm->getPitchNumber()] != *ptADPCM_[bAdpcm->getPitchNumber()]) return false; if (aAdpcm->getPanEnabled() && *panADPCM_[aAdpcm->getPanNumber()] != *panADPCM_[bAdpcm->getPanNumber()]) return false; return true; } //----- Drumkit methods ----- void InstrumentsManager::setInstrumentDrumkitSamplesEnabled(int instNum, int key, bool enabled) { auto kit = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (enabled) { kit->setSampleEnabled(key, true); sampADPCM_.at(static_cast(kit->getSampleNumber(key)))->registerUserInstrument(instNum); } else { sampADPCM_.at(static_cast(kit->getSampleNumber(key)))->deregisterUserInstrument(instNum); kit->setSampleEnabled(key, false); } } bool InstrumentsManager::getInstrumentDrumkitSamplesEnabled(int instNum, int key) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getSampleEnabled(key); } void InstrumentsManager::setInstrumentDrumkitSamples(int instNum, int key, int sampNum) { auto kit = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); sampADPCM_.at(static_cast(kit->getSampleNumber(key)))->deregisterUserInstrument(instNum); sampADPCM_.at(static_cast(sampNum))->registerUserInstrument(instNum); kit->setSampleNumber(key, sampNum); } int InstrumentsManager::getInstrumentDrumkitSamples(int instNum, int key) { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getSampleNumber(key); } void InstrumentsManager::setInstrumentDrumkitPitch(int instNum, int key, int pitch) { std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->setPitch(key, pitch); } void InstrumentsManager::setInstrumentDrumkitPan(int instNum, int key, int pan) { std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->setPan(key, pan); } bool InstrumentsManager::equalPropertiesDrumkit(std::shared_ptr a, std::shared_ptr b) const { auto aKit = std::dynamic_pointer_cast(a); auto bKit = std::dynamic_pointer_cast(b); std::vector aKeys = aKit->getAssignedKeys(); std::vector bKeys = bKit->getAssignedKeys(); if (aKeys.size() != bKeys.size()) return false; std::sort(aKeys.begin(), aKeys.end()); std::sort(bKeys.begin(), bKeys.end()); if (!std::includes(aKeys.cbegin(), aKeys.cend(), bKeys.cbegin(), bKeys.cend())) return false; for (const int& key : aKeys) { if (*sampADPCM_[aKit->getSampleNumber(key)] != *sampADPCM_[bKit->getSampleNumber(key)]) return false; if (aKit->getPitch(key) != bKit->getPitch(key)) return false; if (aKit->getPan(key) != bKit->getPan(key)) return false; } return true; } BambooTracker-0.6.5/BambooTracker/instrument/instruments_manager.hpp000066400000000000000000000571031476276175200260050ustar00rootroot00000000000000/* * Copyright (C) 2018-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include #include #include "instrument.hpp" #include "envelope_fm.hpp" #include "lfo_fm.hpp" #include "sample_adpcm.hpp" #include "sequence_property.hpp" #include "instrument_property_defs.hpp" #include "enum_hash.hpp" enum class InstrumentType; class AbstractInstrument; class InstrumentFM; enum class FMEnvelopeParameter; class EnvelopeFM; class InstrumentSSG; class InstrumentADPCM; class InstrumentDrumkit; class InstrumentsManager { public: explicit InstrumentsManager(bool unedited); void addInstrument(int instNum, InstrumentType type, const std::string& name); void addInstrument(AbstractInstrument* newInstPtr); std::unique_ptr removeInstrument(int instNum); void cloneInstrument(int cloneInstNum, int resInstNum); void deepCloneInstrument(int cloneInstNum, int resInstNum); void swapInstruments(int inst1Num, int inst2Num); std::shared_ptr getInstrumentSharedPtr(int instNum); void clearAll(); std::vector getInstrumentIndices() const; void setInstrumentName(int instNum, const std::string& name); std::string getInstrumentName(int instNum) const; std::vector getInstrumentNameList() const; void clearUnusedInstrumentProperties(); int findFirstFreeInstrument() const; std::unordered_map getDuplicateInstrumentMap() const; inline void setPropertyFindMode(bool unedited) noexcept { regardingUnedited_ = unedited; } private: std::array, 128> insts_; bool regardingUnedited_; //----- FM methods ----- public: void setInstrumentFMEnvelope(int instNum, int envNum); int getInstrumentFMEnvelope(int instNum) const; void setEnvelopeFMParameter(int envNum, FMEnvelopeParameter param, int value); int getEnvelopeFMParameter(int envNum, FMEnvelopeParameter param) const; void setEnvelopeFMOperatorEnabled(int envNum, int opNum, bool enabled); bool getEnvelopeFMOperatorEnabled(int envNum, int opNum) const; std::multiset getEnvelopeFMUsers(int envNum) const; std::vector getEnvelopeFMEntriedIndices() const; int findFirstAssignableEnvelopeFM() const; void setInstrumentFMLFOEnabled(int instNum, bool enabled); bool getInstrumentFMLFOEnabled(int instNum) const; void setInstrumentFMLFO(int instNum, int lfoNum); int getInstrumentFMLFO(int instNum) const; void setLFOFMParameter(int lfoNum, FMLFOParameter param, int value); int getLFOFMparameter(int lfoNum, FMLFOParameter param) const; std::multiset getLFOFMUsers(int lfoNum) const; std::vector getLFOFMEntriedIndices() const; int findFirstAssignableLFOFM() const; void setInstrumentFMOperatorSequenceEnabled(int instNum, FMEnvelopeParameter param, bool enabled); bool getInstrumentFMOperatorSequenceEnabled(int instNum, FMEnvelopeParameter param) const; void setInstrumentFMOperatorSequence(int instNum, FMEnvelopeParameter param, int opSeqNum); int getInstrumentFMOperatorSequence(int instNum, FMEnvelopeParameter param); void addOperatorSequenceFMSequenceData(FMEnvelopeParameter param, int opSeqNum, int data); void removeOperatorSequenceFMSequenceData(FMEnvelopeParameter param, int opSeqNum); void setOperatorSequenceFMSequenceData(FMEnvelopeParameter param, int opSeqNum, int cnt, int data); std::vector getOperatorSequenceFMSequence(FMEnvelopeParameter param, int opSeqNum); void addOperatorSequenceFMLoop(FMEnvelopeParameter param, int opSeqNum, const InstrumentSequenceLoop& loop); void removeOperatorSequenceFMLoop(FMEnvelopeParameter param, int opSeqNum, int begin, int end); void changeOperatorSequenceFMLoop(FMEnvelopeParameter param, int opSeqNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearOperatorSequenceFMLoops(FMEnvelopeParameter param, int opSeqNum); InstrumentSequenceLoopRoot getOperatorSequenceFMLoopRoot(FMEnvelopeParameter param, int opSeqNum) const; void setOperatorSequenceFMRelease(FMEnvelopeParameter param, int opSeqNum, const InstrumentSequenceRelease& release); InstrumentSequenceRelease getOperatorSequenceFMRelease(FMEnvelopeParameter param, int opSeqNum) const; FMOperatorSequenceIter getOperatorSequenceFMIterator(FMEnvelopeParameter param, int opSeqNum) const; std::multiset getOperatorSequenceFMUsers(FMEnvelopeParameter param, int opSeqNum) const; std::vector getOperatorSequenceFMEntriedIndices(FMEnvelopeParameter param) const; int findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter param) const; void setInstrumentFMArpeggioEnabled(int instNum, FMOperatorType op, bool enabled); bool getInstrumentFMArpeggioEnabled(int instNum, FMOperatorType op) const; void setInstrumentFMArpeggio(int instNum, FMOperatorType op, int arpNum); int getInstrumentFMArpeggio(int instNum, FMOperatorType op); void setArpeggioFMType(int arpNum, SequenceType type); SequenceType getArpeggioFMType(int arpNum) const; void addArpeggioFMSequenceData(int arpNum, int data); void removeArpeggioFMSequenceData(int arpNum); void setArpeggioFMSequenceData(int arpNum, int cnt, int data); std::vector getArpeggioFMSequence(int arpNum); void addArpeggioFMLoop(int arpNum, const InstrumentSequenceLoop& loop); void removeArpeggioFMLoop(int arpNum, int begin, int end); void changeArpeggioFMLoop(int arpNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearArpeggioFMLoops(int arpNum); InstrumentSequenceLoopRoot getArpeggioFMLoopRoot(int arpNum) const; void setArpeggioFMRelease(int arpNum, const InstrumentSequenceRelease& release); InstrumentSequenceRelease getArpeggioFMRelease(int arpNum) const; ArpeggioIter getArpeggioFMIterator(int arpNum) const; std::multiset getArpeggioFMUsers(int arpNum) const; std::vector getArpeggioFMEntriedIndices() const; int findFirstAssignableArpeggioFM() const; void setInstrumentFMPitchEnabled(int instNum, FMOperatorType op, bool enabled); bool getInstrumentFMPitchEnabled(int instNum, FMOperatorType op) const; void setInstrumentFMPitch(int instNum, FMOperatorType op, int ptNum); int getInstrumentFMPitch(int instNum, FMOperatorType op); void setPitchFMType(int ptNum, SequenceType type); SequenceType getPitchFMType(int ptNum) const; void addPitchFMSequenceData(int ptNum, int data); void removePitchFMSequenceData(int ptNum); void setPitchFMSequenceData(int ptNum, int cnt, int data); std::vector getPitchFMSequence(int ptNum); void addPitchFMLoop(int ptNum, const InstrumentSequenceLoop& loop); void removePitchFMLoop(int ptNum, int begin, int end); void changePitchFMLoop(int ptNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearPitchFMLoops(int ptNum); InstrumentSequenceLoopRoot getPitchFMLoopRoot(int ptNum) const; void setPitchFMRelease(int ptNum, const InstrumentSequenceRelease& release); InstrumentSequenceRelease getPitchFMRelease(int ptNum) const; PitchIter getPitchFMIterator(int ptNum) const; std::multiset getPitchFMUsers(int ptNum) const; std::vector getPitchFMEntriedIndices() const; int findFirstAssignablePitchFM() const; void setInstrumentFMPanEnabled(int instNum, bool enabled); bool getInstrumentFMPanEnabled(int instNum) const; void setInstrumentFMPan(int instNum, int panNum); int getInstrumentFMPan(int instNum); void addPanFMSequenceData(int panNum, int data); void removePanFMSequenceData(int panNum); void setPanFMSequenceData(int panNum, int cnt, int data); std::vector getPanFMSequence(int panNum); void addPanFMLoop(int panNum, const InstrumentSequenceLoop& loop); void removePanFMLoop(int panNum, int begin, int end); void changePanFMLoop(int panNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearPanFMLoops(int panNum); InstrumentSequenceLoopRoot getPanFMLoopRoot(int panNum) const; void setPanFMRelease(int panNum, const InstrumentSequenceRelease& release); InstrumentSequenceRelease getPanFMRelease(int panNum) const; PanIter getPanFMIterator(int panNum) const; std::multiset getPanFMUsers(int panNum) const; std::vector getPanFMEntriedIndices() const; int findFirstAssignablePanFM() const; void setInstrumentFMEnvelopeResetEnabled(int instNum, FMOperatorType op, bool enabled); private: std::array, 128> envFM_; std::array, 128> lfoFM_; std::unordered_map>, 128>> opSeqFM_; std::array>, 128> arpFM_; std::array>, 128> ptFM_; std::array>, 128> panFM_; bool equalPropertiesFM(std::shared_ptr a, std::shared_ptr b) const; //----- SSG methods ----- public: void setInstrumentSSGWaveformEnabled(int instNum, bool enabled); bool getInstrumentSSGWaveformEnabled(int instNum) const; void setInstrumentSSGWaveform(int instNum, int wfNum); int getInstrumentSSGWaveform(int instNum); void addWaveformSSGSequenceData(int wfNum, const SSGWaveformUnit& data); void removeWaveformSSGSequenceData(int wfNum); void setWaveformSSGSequenceData(int wfNum, int cnt, const SSGWaveformUnit& data); std::vector getWaveformSSGSequence(int wfNum); void addWaveformSSGLoop(int wfNum, const InstrumentSequenceLoop& loop); void removeWaveformSSGLoop(int wfNum, int begin, int end); void changeWaveformSSGLoop(int wfNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearWaveformSSGLoops(int wfNum); InstrumentSequenceLoopRoot getWaveformSSGLoopRoot(int wfNum) const; void setWaveformSSGRelease(int wfNum, const InstrumentSequenceRelease& release); InstrumentSequenceRelease getWaveformSSGRelease(int wfNum) const; SSGWaveformIter getWaveformSSGIterator(int wfNum) const; std::multiset getWaveformSSGUsers(int wfNum) const; std::vector getWaveformSSGEntriedIndices() const; int findFirstAssignableWaveformSSG() const; void setInstrumentSSGToneNoiseEnabled(int instNum, bool enabled); bool getInstrumentSSGToneNoiseEnabled(int instNum) const; void setInstrumentSSGToneNoise(int instNum, int tnNum); int getInstrumentSSGToneNoise(int instNum); void addToneNoiseSSGSequenceData(int tnNum, int data); void removeToneNoiseSSGSequenceData(int tnNum); void setToneNoiseSSGSequenceData(int tnNum, int cnt, int data); std::vector getToneNoiseSSGSequence(int tnNum); void addToneNoiseSSGLoop(int tnNum, const InstrumentSequenceLoop& loop); void removeToneNoiseSSGLoop(int tnNum, int begin, int end); void changeToneNoiseSSGLoop(int tnNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearToneNoiseSSGLoops(int tnNum); InstrumentSequenceLoopRoot getToneNoiseSSGLoopRoot(int tnNum) const; void setToneNoiseSSGRelease(int tnNum, const InstrumentSequenceRelease& release); InstrumentSequenceRelease getToneNoiseSSGRelease(int tnNum) const; SSGToneNoiseIter getToneNoiseSSGIterator(int tnNum) const; std::multiset getToneNoiseSSGUsers(int tnNum) const; std::vector getToneNoiseSSGEntriedIndices() const; int findFirstAssignableToneNoiseSSG() const; void setInstrumentSSGEnvelopeEnabled(int instNum, bool enabled); bool getInstrumentSSGEnvelopeEnabled(int instNum) const; void setInstrumentSSGEnvelope(int instNum, int envNum); int getInstrumentSSGEnvelope(int instNum); void addEnvelopeSSGSequenceData(int envNum, const SSGEnvelopeUnit& data); void removeEnvelopeSSGSequenceData(int envNum); void setEnvelopeSSGSequenceData(int envNum, int cnt, const SSGEnvelopeUnit& data); std::vector getEnvelopeSSGSequence(int envNum); void addEnvelopeSSGLoop(int envNum, const InstrumentSequenceLoop& loop); void removeEnvelopeSSGLoop(int envNum, int begin, int end); void changeEnvelopeSSGLoop(int envNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearEnvelopeSSGLoops(int envNum); InstrumentSequenceLoopRoot getEnvelopeSSGLoopRoot(int envNum) const; void setEnvelopeSSGRelease(int envNum, const InstrumentSequenceRelease& release); InstrumentSequenceRelease getEnvelopeSSGRelease(int envNum) const; SSGEnvelopeIter getEnvelopeSSGIterator(int envNum) const; std::multiset getEnvelopeSSGUsers(int envNum) const; std::vector getEnvelopeSSGEntriedIndices() const; int findFirstAssignableEnvelopeSSG() const; void setInstrumentSSGArpeggioEnabled(int instNum, bool enabled); bool getInstrumentSSGArpeggioEnabled(int instNum) const; void setInstrumentSSGArpeggio(int instNum, int arpNum); int getInstrumentSSGArpeggio(int instNum); void setArpeggioSSGType(int arpNum, SequenceType type); SequenceType getArpeggioSSGType(int arpNum) const; void addArpeggioSSGSequenceData(int arpNum, int data); void removeArpeggioSSGSequenceData(int arpNum); void setArpeggioSSGSequenceData(int arpNum, int cnt, int data); std::vector getArpeggioSSGSequence(int arpNum); void addArpeggioSSGLoop(int arpNum, const InstrumentSequenceLoop& loop); void removeArpeggioSSGLoop(int arpNum, int begin, int end); void changeArpeggioSSGLoop(int arpNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearArpeggioSSGLoops(int arpNum); InstrumentSequenceLoopRoot getArpeggioSSGLoopRoot(int arpNum) const; void setArpeggioSSGRelease(int arpNum, const InstrumentSequenceRelease& release); InstrumentSequenceRelease getArpeggioSSGRelease(int arpNum) const; ArpeggioIter getArpeggioSSGIterator(int arpNum) const; std::multiset getArpeggioSSGUsers(int arpNum) const; std::vector getArpeggioSSGEntriedIndices() const; int findFirstAssignableArpeggioSSG() const; void setInstrumentSSGPitchEnabled(int instNum, bool enabled); bool getInstrumentSSGPitchEnabled(int instNum) const; void setInstrumentSSGPitch(int instNum, int ptNum); int getInstrumentSSGPitch(int instNum); void setPitchSSGType(int ptNum, SequenceType type); SequenceType getPitchSSGType(int ptNum) const; void addPitchSSGSequenceData(int ptNum, int data); void removePitchSSGSequenceData(int ptNum); void setPitchSSGSequenceData(int ptNum, int cnt, int data); std::vector getPitchSSGSequence(int ptNum); void addPitchSSGLoop(int ptNum, const InstrumentSequenceLoop& loop); void removePitchSSGLoop(int ptNum, int begin, int end); void changePitchSSGLoop(int ptNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearPitchSSGLoops(int ptNum); InstrumentSequenceLoopRoot getPitchSSGLoopRoot(int ptNum) const; void setPitchSSGRelease(int ptNum, const InstrumentSequenceRelease& release); InstrumentSequenceRelease getPitchSSGRelease(int ptNum) const; PitchIter getPitchSSGIterator(int ptNum) const; std::multiset getPitchSSGUsers(int ptNum) const; std::vector getPitchSSGEntriedIndices() const; int findFirstAssignablePitchSSG() const; private: std::array>, 128> wfSSG_; std::array>, 128> envSSG_; std::array>, 128> tnSSG_; std::array>, 128> arpSSG_; std::array>, 128> ptSSG_; bool equalPropertiesSSG(std::shared_ptr a, std::shared_ptr b) const; //----- ADPCM methods ----- public: void setInstrumentADPCMSample(int instNum, int sampNum); int getInstrumentADPCMSample(int instNum); void setSampleADPCMRootKeyNumber(int sampNum, int n); int getSampleADPCMRootKeyNumber(int sampNum) const; void setSampleADPCMRootDeltaN(int sampNum, int dn); int getSampleADPCMRootDeltaN(int sampNum) const; void setSampleADPCMRepeatEnabled(int sampNum, bool enabled); bool isSampleADPCMRepeatable(int sampNum) const; bool setSampleADPCMRepeatrange(int sampNum, const SampleRepeatRange& range); SampleRepeatFlag getSampleADPCMRepeatFlag(int sampNum) const; SampleRepeatRange getSampleADPCMRepeatRange(int sampNum) const; void storeSampleADPCMRawSample(int sampNum, const std::vector& sample); void storeSampleADPCMRawSample(int sampNum, std::vector&& sample); void clearSampleADPCMRawSample(int sampNum); std::vector getSampleADPCMRawSample(int sampNum) const; void setSampleADPCMStartAddress(int sampNum, size_t addr); size_t getSampleADPCMStartAddress(int sampNum) const; void setSampleADPCMStopAddress(int sampNum, size_t addr); size_t getSampleADPCMStopAddress(int sampNum) const; std::multiset getSampleADPCMUsers(int sampNum) const; std::vector getSampleADPCMEntriedIndices() const; std::vector getSampleADPCMValidIndices() const; void clearUnusedSamplesADPCM(); int findFirstAssignableSampleADPCM(int startIndex = 0) const; void setInstrumentADPCMEnvelopeEnabled(int instNum, bool enabled); bool getInstrumentADPCMEnvelopeEnabled(int instNum) const; void setInstrumentADPCMEnvelope(int instNum, int envNum); int getInstrumentADPCMEnvelope(int instNum); void addEnvelopeADPCMSequenceData(int envNum, int data); void removeEnvelopeADPCMSequenceData(int envNum); void setEnvelopeADPCMSequenceData(int envNum, int cnt, int data); std::vector getEnvelopeADPCMSequence(int envNum); void addEnvelopeADPCMLoop(int envNum, const InstrumentSequenceLoop& loop); void removeEnvelopeADPCMLoop(int envNum, int begin, int end); void changeEnvelopeADPCMLoop(int envNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearEnvelopeADPCMLoops(int envNum); InstrumentSequenceLoopRoot getEnvelopeADPCMLoopRoot(int envNum) const; void setEnvelopeADPCMRelease(int envNum, const InstrumentSequenceRelease& release); InstrumentSequenceRelease getEnvelopeADPCMRelease(int envNum) const; ADPCMEnvelopeIter getEnvelopeADPCMIterator(int envNum) const; std::multiset getEnvelopeADPCMUsers(int envNum) const; std::vector getEnvelopeADPCMEntriedIndices() const; int findFirstAssignableEnvelopeADPCM() const; void setInstrumentADPCMArpeggioEnabled(int instNum, bool enabled); bool getInstrumentADPCMArpeggioEnabled(int instNum) const; void setInstrumentADPCMArpeggio(int instNum, int arpNum); int getInstrumentADPCMArpeggio(int instNum); void setArpeggioADPCMType(int arpNum, SequenceType type); SequenceType getArpeggioADPCMType(int arpNum) const; void addArpeggioADPCMSequenceData(int arpNum, int data); void removeArpeggioADPCMSequenceData(int arpNum); void setArpeggioADPCMSequenceData(int arpNum, int cnt, int data); std::vector getArpeggioADPCMSequence(int arpNum); void addArpeggioADPCMLoop(int arpNum, const InstrumentSequenceLoop& loop); void removeArpeggioADPCMLoop(int arpNum, int begin, int end); void changeArpeggioADPCMLoop(int arpNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearArpeggioADPCMLoops(int arpNum); InstrumentSequenceLoopRoot getArpeggioADPCMLoopRoot(int arpNum) const; void setArpeggioADPCMRelease(int arpNum, const InstrumentSequenceRelease& release); InstrumentSequenceRelease getArpeggioADPCMRelease(int arpNum) const; ArpeggioIter getArpeggioADPCMIterator(int arpNum) const; std::multiset getArpeggioADPCMUsers(int arpNum) const; std::vector getArpeggioADPCMEntriedIndices() const; int findFirstAssignableArpeggioADPCM() const; void setInstrumentADPCMPitchEnabled(int instNum, bool enabled); bool getInstrumentADPCMPitchEnabled(int instNum) const; void setInstrumentADPCMPitch(int instNum, int ptNum); int getInstrumentADPCMPitch(int instNum); void setPitchADPCMType(int ptNum, SequenceType type); SequenceType getPitchADPCMType(int ptNum) const; void addPitchADPCMSequenceData(int ptNum, int data); void removePitchADPCMSequenceData(int ptNum); void setPitchADPCMSequenceData(int ptNum, int cnt, int data); std::vector getPitchADPCMSequence(int ptNum); void addPitchADPCMLoop(int ptNum, const InstrumentSequenceLoop& loop); void removePitchADPCMLoop(int ptNum, int begin, int end); void changePitchADPCMLoop(int ptNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearPitchADPCMLoops(int ptNum); InstrumentSequenceLoopRoot getPitchADPCMLoopRoot(int ptNum) const; void setPitchADPCMRelease(int ptNum, const InstrumentSequenceRelease& release); InstrumentSequenceRelease getPitchADPCMRelease(int ptNum) const; PitchIter getPitchADPCMIterator(int ptNum) const; std::multiset getPitchADPCMUsers(int ptNum) const; std::vector getPitchADPCMEntriedIndices() const; int findFirstAssignablePitchADPCM() const; void setInstrumentADPCMPanEnabled(int instNum, bool enabled); bool getInstrumentADPCMPanEnabled(int instNum) const; void setInstrumentADPCMPan(int instNum, int panNum); int getInstrumentADPCMPan(int instNum); void addPanADPCMSequenceData(int panNum, int data); void removePanADPCMSequenceData(int panNum); void setPanADPCMSequenceData(int panNum, int cnt, int data); std::vector getPanADPCMSequence(int panNum); void addPanADPCMLoop(int panNum, const InstrumentSequenceLoop& loop); void removePanADPCMLoop(int panNum, int begin, int end); void changePanADPCMLoop(int panNum, int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void clearPanADPCMLoops(int panNum); InstrumentSequenceLoopRoot getPanADPCMLoopRoot(int panNum) const; void setPanADPCMRelease(int panNum, const InstrumentSequenceRelease& release); InstrumentSequenceRelease getPanADPCMRelease(int panNum) const; PanIter getPanADPCMIterator(int panNum) const; std::multiset getPanADPCMUsers(int panNum) const; std::vector getPanADPCMEntriedIndices() const; int findFirstAssignablePanADPCM() const; private: std::array, 128> sampADPCM_; std::array>, 128> envADPCM_; std::array>, 128> arpADPCM_; std::array>, 128> ptADPCM_; std::array>, 128> panADPCM_; bool equalPropertiesADPCM(std::shared_ptr a, std::shared_ptr b) const; //----- Drumkit methods ----- public: void setInstrumentDrumkitSamplesEnabled(int instNum, int key, bool enabled); bool getInstrumentDrumkitSamplesEnabled(int instNum, int key) const; void setInstrumentDrumkitSamples(int instNum, int key, int sampNum); int getInstrumentDrumkitSamples(int instNum, int key); void setInstrumentDrumkitPitch(int instNum, int key, int pitch); void setInstrumentDrumkitPan(int instNum, int key, int pan); private: bool equalPropertiesDrumkit(std::shared_ptr a, std::shared_ptr b) const; }; BambooTracker-0.6.5/BambooTracker/instrument/lfo_fm.cpp000066400000000000000000000037221476276175200231530ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "lfo_fm.hpp" namespace { const std::unordered_map DEF_PARAMS = { { FMLFOParameter::FREQ, 0 }, { FMLFOParameter::PMS, 0 }, { FMLFOParameter::AMS, 0 }, { FMLFOParameter::AM1, 0 }, { FMLFOParameter::AM2, 0 }, { FMLFOParameter::AM3, 0 }, { FMLFOParameter::AM4, 0 }, { FMLFOParameter::Count, 0 } }; } LFOFM::LFOFM(int n) : AbstractInstrumentProperty (n) { clearParameters(); } std::unique_ptr LFOFM::clone() { std::unique_ptr clone = std::make_unique(*this); clone->clearUserInstruments(); return clone; } void LFOFM::setParameterValue(FMLFOParameter param, int value) { params_.at(param) = value; } int LFOFM::getParameterValue(FMLFOParameter param) const { return params_.at(param); } bool LFOFM::isEdited() const { return params_ != DEF_PARAMS; } void LFOFM::clearParameters() { params_ = DEF_PARAMS; } BambooTracker-0.6.5/BambooTracker/instrument/lfo_fm.hpp000066400000000000000000000035071476276175200231610ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include "abstract_instrument_property.hpp" #include "enum_hash.hpp" enum class FMLFOParameter { FREQ, AMS, PMS, Count, AM1, AM2, AM3, AM4 }; class LFOFM final : public AbstractInstrumentProperty { public: explicit LFOFM(int n); friend bool operator==(const LFOFM& a, const LFOFM& b) { return a.params_ == b.params_; } friend bool operator!=(const LFOFM& a, const LFOFM& b) { return !(a == b); } std::unique_ptr clone(); void setParameterValue(FMLFOParameter param, int value); int getParameterValue(FMLFOParameter param) const; bool isEdited() const override; void clearParameters() override; private: std::unordered_map params_; }; BambooTracker-0.6.5/BambooTracker/instrument/sample_adpcm.cpp000066400000000000000000000056171476276175200243430ustar00rootroot00000000000000/* * Copyright (C) 2020-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "sample_adpcm.hpp" #include namespace { constexpr int DEF_RT_DELTAN_ = 0x49cd; // 16000Hz constexpr bool DEF_REPET_ = false; } SampleADPCM::SampleADPCM(int num) : AbstractInstrumentProperty (num), repeatRange_(0, 0) { clearParameters(); } bool operator==(const SampleADPCM& a, const SampleADPCM& b) { return (a.rootKeyNum_ == b.rootKeyNum_ && a.rootDeltaN_ == b.rootDeltaN_ && a.isRepeated_ == b.isRepeated_ && a.sample_ == b.sample_); } std::unique_ptr SampleADPCM::clone() { std::unique_ptr clone = std::make_unique(*this); clone->clearUserInstruments(); return clone; } void SampleADPCM::clearSample() { startAddress_ = 0; stopAddress_ = 0; sample_ = std::vector(1); repeatRange_ = SampleRepeatRange(0, (sample_.size() - 1) >> 5); // By 32 bytes } bool SampleADPCM::isEdited() const { if (rootKeyNum_ != DEF_ROOT_KEY || rootDeltaN_ != DEF_RT_DELTAN_ || isRepeated_ != DEF_REPET_ || sample_.size() != 1 || sample_.front() != 0) return true; return false; } void SampleADPCM::clearParameters() { clearSample(); rootKeyNum_ = DEF_ROOT_KEY; rootDeltaN_ = DEF_RT_DELTAN_; isRepeated_ = DEF_REPET_; } bool SampleADPCM::setRepeatRange(const SampleRepeatRange& range) noexcept { if (sample_.size() <= range.last()) { return false; } repeatRange_ = range; return true; } bool SampleADPCM::storeSample(const std::vector& sample) { if (sample.empty()) return false; repeatRange_ = repeatRange_.clampLast((sample.size() - 1) >> 5); // By 32 bytes sample_ = sample; return true; } bool SampleADPCM::storeSample(std::vector&& sample) { if (sample.empty()) return false; repeatRange_ = repeatRange_.clampLast((sample.size() - 1) >> 5); // By 32 bytes sample_ = std::move(sample); return true; } BambooTracker-0.6.5/BambooTracker/instrument/sample_adpcm.hpp000066400000000000000000000065011476276175200243410ustar00rootroot00000000000000/* * Copyright (C) 2020-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include #include "abstract_instrument_property.hpp" #include "sample_repeat.hpp" class SampleADPCM final : public AbstractInstrumentProperty { public: explicit SampleADPCM(int num); friend bool operator==(const SampleADPCM& a, const SampleADPCM& b); friend bool operator!=(const SampleADPCM& a, const SampleADPCM& b) { return !(a == b); } std::unique_ptr clone(); void setRootKeyNumber(int n) noexcept { rootKeyNum_ = n; } int getRootKeyNumber() const noexcept {return rootKeyNum_; } void setRootDeltaN(int dn) noexcept { rootDeltaN_ = dn; } int getRootDeltaN() const noexcept { return rootDeltaN_; } void setRepeatEnabled(bool enabled) noexcept { isRepeated_ = enabled; } bool isRepeatable() const noexcept { return isRepeated_; } SampleRepeatFlag getRepeatFlag() const noexcept { if (!isRepeated_) return SampleRepeatFlag::Disabled; int flags = SampleRepeatFlag::ShouldRepeat; if (repeatRange_.first() != 0) { flags |= SampleRepeatFlag::ShouldRewriteStart; } if (repeatRange_.last() != sample_.size() - 1) { flags |= SampleRepeatFlag::ShouldRewriteStop; } return static_cast(flags); } bool setRepeatRange(const SampleRepeatRange& range) noexcept; SampleRepeatRange getRepeatRange() const noexcept { return repeatRange_; } bool storeSample(const std::vector& sample); bool storeSample(std::vector&& sample); std::vector getSamples() const { return sample_; } void clearSample(); void setStartAddress(size_t addr) noexcept { startAddress_ = addr; } size_t getStartAddress() const noexcept { return startAddress_; } void setStopAddress(size_t addr) noexcept { stopAddress_ = addr; } size_t getStopAddress() const noexcept { return stopAddress_; } bool isEdited() const override; void clearParameters() override; static constexpr int DEF_ROOT_KEY = 60; // C5 static int calculateADPCMDeltaN(unsigned int rate) { return static_cast(std::round((rate << 16) / 55500.)); } private: int rootKeyNum_, rootDeltaN_; bool isRepeated_; /// Range (first byte, last byte) SampleRepeatRange repeatRange_; std::vector sample_; size_t startAddress_, stopAddress_; }; BambooTracker-0.6.5/BambooTracker/instrument/sample_repeat.hpp000066400000000000000000000043151476276175200245360ustar00rootroot00000000000000/* * Copyright (C) 2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include /// Repeat type enum SampleRepeatFlag : int { ShouldRewriteStart = 0b001, ShouldRewriteStop = 0b010, ShouldRepeat = 0b100, /* * |---| repeat range * Disabled: | | * LeftPartial: |-----| | * RightPartial: | |-----| * MiddlePartial: | |----| | * Simple: |--------| */ Disabled = 0, LeftPartial = ShouldRepeat | ShouldRewriteStop, RightPartial = ShouldRepeat | ShouldRewriteStart, MiddlePartial = ShouldRepeat | ShouldRewriteStart | ShouldRewriteStop, Simple = ShouldRepeat, }; /// Immutable safe range class /// handles position as ADPCM memory address by 32 bytes class SampleRepeatRange { public: SampleRepeatRange(size_t first, size_t last) { if (last < first) { throw std::invalid_argument("Invalid range"); } first_ = first; last_ = last; } size_t first() const noexcept { return first_; } size_t last() const noexcept { return last_; } SampleRepeatRange clampLast(size_t last) const noexcept { return SampleRepeatRange(std::min(first_, last), std::min(last_, last)); } private: size_t first_, last_; }; BambooTracker-0.6.5/BambooTracker/instrument/sequence_iterator_interface.hpp000066400000000000000000000036761476276175200274670ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once enum class SequenceType { PlainSequence, AbsoluteSequence, FixedSequence, RelativeSequence }; enum class SequenceIteratorState { NotBegin, Run, RunRelease, End, }; template class SequenceIteratorInterface { public: virtual ~SequenceIteratorInterface() = default; static constexpr int END_SEQ_POS = -1; int pos() const noexcept { return pos_; } bool hasEnded() const noexcept { return pos_ == END_SEQ_POS; } SequenceIteratorState state() const noexcept { return state_; } virtual SequenceType type() const = 0; virtual T data() const = 0; virtual int next() = 0; virtual int front() = 0; virtual int release() = 0; virtual int end() = 0; protected: explicit SequenceIteratorInterface(int initPos = 0) : pos_(initPos), state_(SequenceIteratorState::NotBegin) {} int pos_; SequenceIteratorState state_; }; BambooTracker-0.6.5/BambooTracker/instrument/sequence_property.cpp000066400000000000000000000212221476276175200254600ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "sequence_property.hpp" #include "utils.hpp" InstrumentSequenceBaseUnit::InstrumentSequenceBaseUnit() noexcept : data(ERR_DATA) { } InstrumentSequenceBaseUnit::InstrumentSequenceBaseUnit(int d) noexcept : data(d) { } InstrumentSequenceExtendUnit::InstrumentSequenceExtendUnit() noexcept : InstrumentSequenceBaseUnit(), type(InstrumentSequenceExtendUnit::UnusedSubdata), subdata(InstrumentSequenceBaseUnit::ERR_DATA) { } InstrumentSequenceExtendUnit::InstrumentSequenceExtendUnit(int d, SubdataType subType, int subData) noexcept : InstrumentSequenceBaseUnit(d), type(subType), subdata(subData) { } InstrumentSequenceExtendUnit InstrumentSequenceExtendUnit::makeOnlyDataUnit(int data) noexcept { return InstrumentSequenceExtendUnit(data, SubdataType::UnusedSubdata, ERR_DATA); } InstrumentSequenceExtendUnit InstrumentSequenceExtendUnit::makeRawUnit(int data, int sub) noexcept { return InstrumentSequenceExtendUnit(data, SubdataType::RawSubdata, sub); } InstrumentSequenceExtendUnit InstrumentSequenceExtendUnit::makeRatioUnit(int data, int subFirst, int subSecond) noexcept { return InstrumentSequenceExtendUnit(data, SubdataType::RatioSubdata, ((1 << 16) | (subFirst << 8) | subSecond)); } InstrumentSequenceExtendUnit InstrumentSequenceExtendUnit::makeShiftUnit(int data, int rshift) noexcept { int sub = (rshift > 0) ? ((2 << 16) | rshift) : ((3 << 16) | -rshift); return InstrumentSequenceExtendUnit(data, SubdataType::ShiftSubdata, sub); } InstrumentSequenceExtendUnit InstrumentSequenceExtendUnit::makeUnitWithDecode(int data, int subsrc) noexcept { SubdataType type; if (subsrc & 0x20000) type = SubdataType::ShiftSubdata; else type = (subsrc & 0x10000) ? SubdataType::RatioSubdata : SubdataType::RawSubdata; return InstrumentSequenceExtendUnit(data, type, subsrc); } void InstrumentSequenceExtendUnit::getSubdataAsRaw(int& raw) const noexcept { raw = subdata; } void InstrumentSequenceExtendUnit::getSubdataAsRatio(int& first, int& second) const noexcept { first = (subdata & 0x0ff00) >> 8; second = subdata & 0x000ff; } void InstrumentSequenceExtendUnit::getSubdataAsShift(int& rshift) const noexcept { rshift = (subdata & 0x10000) ? -(0x0ffff & data) : (0x0ffff & data); } namespace { inline InstrumentSequenceLoop::Ptr makeSequenceLoopPtr(const InstrumentSequenceLoop& loop) { return std::make_shared(loop); } } InstrumentSequenceLoop::InstrumentSequenceLoop(int begin, int end, int times) : begin_(begin), end_(end), times_(times) { } void InstrumentSequenceLoop::setBeginPos(int pos) { if (begin_ < pos && hasInnerLoop()) { auto&& it = utils::findIf(childs_, [pos](const auto& pair) { return pos <= pair.second->end_; }); if (it == childs_.end()) { removeAllInnerLoops(); } else { if (it->second->begin_ < pos) it->second->setBeginPos(pos); childs_.erase(childs_.begin(), it); } } begin_ = pos; } void InstrumentSequenceLoop::setEndPos(int pos) { if (pos < end_ && hasInnerLoop()) { auto&& it = std::find_if(childs_.rbegin(), childs_.rend(), [pos](const auto& pair) { return pair.second->begin_ <= pos; }); if (it == childs_.rend()) { removeAllInnerLoops(); } else { if (pos < it->second->end_) it->second->setEndPos(pos); childs_.erase(it.base(), childs_.end()); } } end_ = pos; } bool InstrumentSequenceLoop::addInnerLoop(const InstrumentSequenceLoop& inner) { for (auto& pair : childs_) { Ptr& loop = pair.second; if (loop->isOverlapped(inner)) { if (loop->hasSameRegion(inner)) { loop->times_ = inner.times_; return true; } else if (loop->isContainable(inner)) { return loop->addInnerLoop(inner); } else { // Illegal region return false; } } else if (loop->begin_ < inner.begin_) break; } // Add new region childs_.insert(std::make_pair(inner.begin_, makeSequenceLoopPtr(inner))); return true; } bool InstrumentSequenceLoop::changeInnerLoop(int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { if (hasInnerLoopBeginAt(prevBegin)) { InstrumentSequenceLoop::Ptr lpt = getInnerLoopBeginAt(prevBegin); if (lpt->end_ != prevEnd) return lpt->changeInnerLoop(prevBegin, prevEnd, loop); if (prevBegin != loop.begin_) lpt->setBeginPos(loop.begin_); if (prevEnd != loop.end_) lpt->setEndPos(loop.end_); if (lpt->times_ != loop.times_) lpt->setTimes(loop.times_); return true; } else { return false; } } void InstrumentSequenceLoop::removeInnerLoop(int begin, int end) { for (auto& pair : childs_) { Ptr& loop = pair.second; if (loop->isOverlapped(begin, end)) { if (loop->hasSameRegion(begin, end)) { childs_.erase(pair.first); } else if (loop->isContainable(begin, end)) { loop->removeInnerLoop(begin, end); } return; } else if (loop->begin_ < begin) return; } } void InstrumentSequenceLoop::removeAllInnerLoops() { childs_.clear(); } std::vector InstrumentSequenceLoop::getAllInnerLoops() const { std::vector list; for (const auto& pair : childs_) { list.push_back(*pair.second); auto in = pair.second->getAllInnerLoops(); list.insert(list.end(), in.begin(), in.end()); } return list; } bool InstrumentSequenceLoop::isOverlapped(int begin, int end) const { if (begin_ == begin) return true; if (begin_ < begin) return (begin <= end_); else /* begin < begin_ */ return (begin_ <= end); } bool InstrumentSequenceLoop::isContainable(int begin, int end) const { return ((begin_ < begin && end <= end_) || (begin_ == begin && end < end_)); } bool InstrumentSequenceLoop::hasSameRegion(int begin, int end) const { return (begin_ == begin && end_ == end); } InstrumentSequenceLoop InstrumentSequenceLoop::clone() const { InstrumentSequenceLoop l(begin_, end_, times_); for (const auto& pair : childs_) { l.childs_.insert(std::make_pair(pair.first, makeSequenceLoopPtr(pair.second->clone()))); } return l; } InstrumentSequenceLoopRoot InstrumentSequenceLoopRoot::clone() const { InstrumentSequenceLoopRoot r(end_); for (const auto& pair : childs_) { r.childs_.insert(std::make_pair(pair.first, makeSequenceLoopPtr(pair.second->clone()))); } return r; } namespace inst_utils { LoopStack::StackItem::StackItem(const InstrumentSequenceLoop::Ptr& ptr) : loop(ptr), count(ptr->getTimes()), isInfinite(ptr->isInfinite()) { } LoopStack::LoopStack(const std::shared_ptr& ptr) : stack_{ StackItem(std::static_pointer_cast(ptr)) } { } void LoopStack::clear() { stack_.erase(stack_.begin() + 1, stack_.end()); } void LoopStack::pushLoopsAtPos(int pos) { while (true) { InstrumentSequenceLoop::Ptr& loop = stack_.back().loop; if (!loop->hasInnerLoopBeginAt(pos)) break; stack_.emplace_back(loop->getInnerLoopBeginAt(pos)); } } int LoopStack::checkLoopEndAndNextPos(int curPos) { while (stack_.size() > 1) { StackItem& item = stack_.back(); if (item.loop->getEndPos() != curPos) { return curPos + 1; } if (item.isInfinite) { return item.loop->getBeginPos(); } if (--(item.count)) { return item.loop->getBeginPos(); } else { stack_.pop_back(); } } return curPos + 1; } } InstrumentSequenceRelease::InstrumentSequenceRelease(ReleaseType type, int beginPos) : type_(type), begin_(type_ == ReleaseType::NoRelease ? DISABLE_RELEASE : beginPos) { } void InstrumentSequenceRelease::setType(ReleaseType type) { type_ = type; if (type == ReleaseType::NoRelease) begin_ = DISABLE_RELEASE; } void InstrumentSequenceRelease::setBeginPos(int pos) { if (type_ != ReleaseType::NoRelease) begin_ = pos; } void InstrumentSequenceRelease::disable() { type_ = ReleaseType::NoRelease; begin_ = DISABLE_RELEASE; } BambooTracker-0.6.5/BambooTracker/instrument/sequence_property.hpp000066400000000000000000000342761476276175200255020ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include #include #include #include "abstract_instrument_property.hpp" #include "sequence_iterator_interface.hpp" struct InstrumentSequenceBaseUnit { int data; static constexpr int ERR_DATA = -1; InstrumentSequenceBaseUnit() noexcept; explicit InstrumentSequenceBaseUnit(int d) noexcept; virtual ~InstrumentSequenceBaseUnit() = default; friend bool operator==(const InstrumentSequenceBaseUnit& a, const InstrumentSequenceBaseUnit& b) { return a.data == b.data; } friend bool operator!=(const InstrumentSequenceBaseUnit& a, const InstrumentSequenceBaseUnit& b) { return !(a == b); } }; struct InstrumentSequenceExtendUnit : public InstrumentSequenceBaseUnit { enum SubdataType : int { UnusedSubdata, RawSubdata, RatioSubdata, ShiftSubdata } type; /// - If bit 17 is 0, /// * If bit 16 is 0, bit 0-15 is raw data /// * If bit 16 is 1, bit 0-7 is 2nd and bit 8-15 is 1st ratio value /// - If bit 17 is 1, /// * If bit 16 is 0, bit 0-15 is right shift value /// * If bit 16 is 1, bit 0-15 is left shift value int subdata; InstrumentSequenceExtendUnit() noexcept; explicit InstrumentSequenceExtendUnit(int d, SubdataType subType, int subData) noexcept; static InstrumentSequenceExtendUnit makeOnlyDataUnit(int data) noexcept; static InstrumentSequenceExtendUnit makeRawUnit(int data, int sub) noexcept; static InstrumentSequenceExtendUnit makeRatioUnit(int data, int subFirst, int subSecond) noexcept; static InstrumentSequenceExtendUnit makeShiftUnit(int data, int rshift) noexcept; static InstrumentSequenceExtendUnit makeUnitWithDecode(int data, int subsrc) noexcept; void getSubdataAsRaw(int& raw) const noexcept; void getSubdataAsRatio(int& first, int& second) const noexcept; void getSubdataAsShift(int& rshift) const noexcept; friend bool operator==(const InstrumentSequenceExtendUnit& a, const InstrumentSequenceExtendUnit& b) { return a.data == b.data && a.type == b.type && a.subdata == b.subdata; } friend bool operator!=(const InstrumentSequenceExtendUnit& a, const InstrumentSequenceExtendUnit& b) { return !(a == b); } }; class InstrumentSequenceLoop { public: using Ptr = std::shared_ptr; static constexpr int INFINITE_LOOP = 1; /// Loop in a closed interval [begin, end] InstrumentSequenceLoop(int begin, int end, int times = INFINITE_LOOP); friend bool operator==(const InstrumentSequenceLoop& a, const InstrumentSequenceLoop& b) { return (a.begin_ == b.begin_ && a.end_ == b.end_ && a.times_ == b.times_); } friend bool operator!=(const InstrumentSequenceLoop& a, const InstrumentSequenceLoop& b) { return !(a == b); } void setBeginPos(int pos); inline int getBeginPos() const noexcept { return begin_; } void setEndPos(int pos); inline int getEndPos() const noexcept { return end_; } inline void setTimes(int times) noexcept { times_ = times; } inline int getTimes() const noexcept { return times_; } inline bool isInfinite() const noexcept { return times_ == INFINITE_LOOP; } inline Ptr getInnerLoopBeginAt(int pos) const { return childs_.at(pos); } bool addInnerLoop(const InstrumentSequenceLoop& inner); bool changeInnerLoop(int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop); void removeInnerLoop(int begin, int end); void removeAllInnerLoops(); std::vector getAllInnerLoops()const; inline bool hasInnerLoop() const { return !childs_.empty(); } inline bool hasInnerLoopBeginAt(int pos) const { return childs_.count(pos); } bool isOverlapped(int begin, int end) const; inline bool isOverlapped(const InstrumentSequenceLoop& other) const { return isOverlapped(other.begin_, other.end_); } bool isContainable(int begin, int end) const; inline bool isContainable(const InstrumentSequenceLoop& other) const { return isContainable(other.begin_, other.end_); } bool hasSameRegion(int begin, int end) const; inline bool hasSameRegion(const InstrumentSequenceLoop& other) const { return hasSameRegion(other.begin_, other.end_); } InstrumentSequenceLoop clone() const; protected: int begin_, end_, times_; std::map childs_; }; class InstrumentSequenceLoopRoot : public InstrumentSequenceLoop { public: InstrumentSequenceLoopRoot(int size) : InstrumentSequenceLoop(0, size - 1) {} inline int size() const { return end_; } inline void extend() { setEndPos(end_ + 1); } inline void shrink() { if (end_) setEndPos(end_ - 1); } inline void resize(int size) { setEndPos(size - 1); } inline void clear() { removeAllInnerLoops(); setEndPos(0); } inline Ptr getLoopBeginAt(int pos) const { return getInnerLoopBeginAt(pos); } inline bool addLoop(const InstrumentSequenceLoop& loop) { return addInnerLoop(loop); } inline bool changeLoop(int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { return changeInnerLoop(prevBegin, prevEnd, loop); } inline void removeLoop(int begin, int end) { removeInnerLoop(begin, end); } inline void removeAllLoops() { removeAllInnerLoops(); } inline std::vector getAllLoops() const { return getAllInnerLoops(); } inline bool hasLoop() const { return hasInnerLoop(); } inline bool hasLoopBeginAt(int pos) const { return hasInnerLoopBeginAt(pos); } InstrumentSequenceLoopRoot clone() const; }; namespace inst_utils { class LoopStack { private: struct StackItem { InstrumentSequenceLoop::Ptr loop; int count; bool isInfinite; explicit StackItem(const InstrumentSequenceLoop::Ptr& ptr); }; std::deque stack_; public: explicit LoopStack(const std::shared_ptr& ptr); void clear(); void pushLoopsAtPos(int pos); int checkLoopEndAndNextPos(int curPos); }; } class InstrumentSequenceRelease { public: enum ReleaseType { NoRelease, FixedRelease, AbsoluteRelease, RelativeRelease }; static constexpr int DISABLE_RELEASE = -1; explicit InstrumentSequenceRelease(ReleaseType getType, int getBeginPos = DISABLE_RELEASE); friend bool operator==(const InstrumentSequenceRelease& a, const InstrumentSequenceRelease& b) { return (a.type_ == b.type_ && a.begin_ == b.begin_); } friend bool operator!=(const InstrumentSequenceRelease& a, const InstrumentSequenceRelease& b) { return !(a == b); } inline ReleaseType getType() const noexcept { return type_; } void setType(ReleaseType type); void setBeginPos(int pos); inline int getBeginPos() const noexcept { return begin_; } inline bool isEnabled() const noexcept { return (type_ != ReleaseType::NoRelease && begin_ != DISABLE_RELEASE); } void disable(); private: ReleaseType type_; int begin_; }; template class InstrumentSequenceProperty final : public AbstractInstrumentProperty { public: InstrumentSequenceProperty(int num, SequenceType defaultType, const T& defaultUnit, const T& errorUnit, int relReleaseDenom = 1) : AbstractInstrumentProperty(num), DEF_TYPE_(defaultType), DEF_UNIT_(defaultUnit), ERR_UNIT_(errorUnit), REL_RELEASE_DENOM_(relReleaseDenom), loop_(std::make_shared(1)), release_(InstrumentSequenceRelease::NoRelease) { clearParameters(); } friend bool operator==(const InstrumentSequenceProperty& a, const InstrumentSequenceProperty& b) { return (a.type_ == b.type_ && a.seq_ == b.seq_ && a.loop_ == b.loop_ && a.release_ == b.release_); } friend bool operator!=(const InstrumentSequenceProperty& a, const InstrumentSequenceProperty& b) { return !(a == b); } std::unique_ptr clone() { std::unique_ptr clone = std::make_unique(*this); clone->clearUserInstruments(); return clone; } inline void setType(SequenceType type) noexcept { type_ = type; } inline SequenceType getType() const noexcept { return type_; } bool isEdited() const override { return (seq_.size() > 1 || seq_.front() != DEF_UNIT_ || loop_->hasLoop() || release_.getType() != InstrumentSequenceRelease::NoRelease); } void clearParameters() override { type_ = DEF_TYPE_; seq_ = { DEF_UNIT_ }; loop_->clear(); release_.disable(); } //***** Sequence ***** inline size_t getSequenceSize() const { return seq_.size(); } T getSequenceUnit(int n) const { return seq_.at(static_cast(n)); } inline std::vector getSequence() const { return seq_; } void addSequenceUnit(const T& unit) { seq_.push_back(unit); loop_->extend(); } void removeSequenceUnit() { seq_.pop_back(); loop_->shrink(); if (release_.getBeginPos() == static_cast(seq_.size())) release_.disable(); } void setSequenceUnit(int n, const T& unit) { seq_.at(static_cast(n)) = unit; } //***** Loop ***** inline InstrumentSequenceLoopRoot getLoopRoot() const { return *loop_; } inline void addLoop(const InstrumentSequenceLoop& loop) const { loop_->addLoop(loop); } inline void removeLoop(int begin, int end) const { loop_->removeLoop(begin, end); } inline void clearLoops() const { loop_->removeAllLoops(); } inline void changeLoop(int prevBegin, int prevEnd, const InstrumentSequenceLoop& loop) { loop_->changeLoop(prevBegin, prevEnd, loop); } //***** Release ***** InstrumentSequenceRelease getRelease() const noexcept { return release_; } inline void setRelease(const InstrumentSequenceRelease& release) { release_ = release; } class Iterator final : public SequenceIteratorInterface { public: explicit Iterator(const InstrumentSequenceProperty* seqProp) : SequenceIteratorInterface(0), seqProp_(seqProp), loopStack_(seqProp->loop_), isRelease_(false), relReleaseRate_(1.) { } SequenceType type() const override { return seqProp_->type_; } T data() const override { if (this->hasEnded() || static_cast(seqProp_->seq_.size()) <= this->pos_) return seqProp_->ERR_UNIT_; return (isRelease_ ? seqProp_->getSequenceUnit(this->pos_, relReleaseRate_) : seqProp_->getSequenceUnit(this->pos_)); } int next() override { if (this->hasEnded()) return this->END_SEQ_POS; if (this->state_ == SequenceIteratorState::NotBegin) { return this->pos_; } this->pos_ = loopStack_.checkLoopEndAndNextPos(this->pos_); loopStack_.pushLoopsAtPos(this->pos_); // Range check if ((!isRelease_ && seqProp_->release_.isEnabled() && this->pos_ >= seqProp_->release_.getBeginPos()) || this->pos_ >= static_cast(seqProp_->seq_.size())) { this->pos_ = this->END_SEQ_POS; } return this->pos_; } int front() override { this->state_ = SequenceIteratorState::Run; loopStack_.clear(); isRelease_ = false; relReleaseRate_ = 1.; if (seqProp_->release_.getBeginPos()) { this->pos_ = 0; loopStack_.pushLoopsAtPos(0); } else { this->pos_ = this->END_SEQ_POS; } return this->pos_; } int release() override { const std::vector& seq = seqProp_->seq_; const InstrumentSequenceRelease& release = seqProp_->release_; int next = this->END_SEQ_POS; isRelease_ = true; loopStack_.clear(); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: break; case InstrumentSequenceRelease::FixedRelease: { next = release.getBeginPos(); break; } case InstrumentSequenceRelease::AbsoluteRelease: { int crtr; if (this->pos_ == this->END_SEQ_POS) { if (int relBegin = release.getBeginPos()) { crtr = seq[static_cast(relBegin - 1)].data; } else { next = relBegin; break; } } else { crtr = seq[static_cast(this->pos_)].data; } auto&& it = std::find_if(seq.begin() + release.getBeginPos(), seq.end(), [crtr](const T& unit) { return (unit.data <= crtr); }); if (it != seq.end()) next = std::distance(seq.begin(), it); break; } case InstrumentSequenceRelease::RelativeRelease: { next = release.getBeginPos(); if (this->hasEnded()) { if (next) relReleaseRate_ = seq[static_cast(next - 1)].data / seqProp_->REL_RELEASE_DENOM_; } else { relReleaseRate_ = seq[static_cast(this->pos_)].data / seqProp_->REL_RELEASE_DENOM_; } break; } } this->pos_ = next; if (next == this->END_SEQ_POS) { this->state_ = SequenceIteratorState::End; } else { this->state_ = SequenceIteratorState::RunRelease; loopStack_.pushLoopsAtPos(this->pos_); } return this->pos_; } int end() override { this->pos_ = this->END_SEQ_POS; this->state_ = SequenceIteratorState::End; return this->END_SEQ_POS; } private: const InstrumentSequenceProperty* seqProp_; inst_utils::LoopStack loopStack_; bool isRelease_; double relReleaseRate_; }; std::unique_ptr getIterator() { return std::make_unique(this); } private: const SequenceType DEF_TYPE_; const T DEF_UNIT_; const T ERR_UNIT_; const double REL_RELEASE_DENOM_; SequenceType type_; std::vector seq_; std::shared_ptr loop_; InstrumentSequenceRelease release_; inline T getSequenceUnit(int n, double relRate) const { T unit = seq_.at(static_cast(n)); unit.data = static_cast(unit.data * relRate); return unit; } }; BambooTracker-0.6.5/BambooTracker/io/000077500000000000000000000000001476276175200174005ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/io/bank_io.cpp000066400000000000000000000051111476276175200215040ustar00rootroot00000000000000/* * Copyright (C) 2019-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "bank_io.hpp" #include #include "file_io_error.hpp" #include "btb_io.hpp" #include "wopn_io.hpp" #include "ff_io.hpp" #include "ppc_io.hpp" #include "p86_io.hpp" #include "pps_io.hpp" #include "pvi_io.hpp" #include "pzi_io.hpp" #include "dat_io.hpp" #include "pmb_io.hpp" namespace io { AbstractBank* AbstractBankIO::load(const BinaryContainer& ctr) const { (void)ctr; throw FileUnsupportedError(FileType::Bank); } void AbstractBankIO::save(BinaryContainer& ctr, const std::weak_ptr instMan, const std::vector& instNums) const { (void)ctr; (void)instMan; (void)instNums; throw FileUnsupportedError(io::FileType::Bank); } //------------------------------------------------------------ std::unique_ptr BankIO::instance_; BankIO::BankIO() { handler_.add(new BtbIO); handler_.add(new WopnIO); handler_.add(new FfIO); handler_.add(new PpcIO); handler_.add(new P86IO); handler_.add(new PpsIO); handler_.add(new PviIO); handler_.add(new PziIO); handler_.add(new DatIO); handler_.add(new PmbIO); } BankIO& BankIO::getInstance() { if (!instance_) instance_.reset(new BankIO); return *instance_; } void BankIO::saveBank(BinaryContainer& ctr, const std::weak_ptr instMan, const std::vector& instNums) { handler_.at("btb")->save(ctr, instMan, instNums); } AbstractBank* BankIO::loadBank(const BinaryContainer& ctr, const std::string& path) { return handler_.at(getExtension(path))->load(ctr); } } BambooTracker-0.6.5/BambooTracker/io/bank_io.hpp000066400000000000000000000055311476276175200215170ustar00rootroot00000000000000/* * Copyright (C) 2019-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include "instruments_manager.hpp" #include "bank.hpp" #include "binary_container.hpp" #include "io_utils.hpp" namespace io { class AbstractBankIO { public: AbstractBankIO(const std::string& ext, const std::string& desc, bool loadable, bool savable) : ext_(ext), desc_(desc), loadable_(loadable), savable_(savable) {} virtual ~AbstractBankIO() = default; virtual AbstractBank* load(const BinaryContainer& ctr) const; virtual void save(BinaryContainer& ctr, const std::weak_ptr instMan, const std::vector& instNums) const; inline std::string getExtension() const noexcept { return ext_; } inline std::string getFilterText() const { return desc_ + " (*." + ext_ + ")"; } inline bool isLoadable() const noexcept { return loadable_; } inline bool isSavable() const noexcept { return savable_; } private: const std::string ext_, desc_; bool loadable_, savable_; }; class BankIO { public: static BankIO& getInstance(); void saveBank(BinaryContainer& ctr, const std::weak_ptr instMan, const std::vector& instNums); AbstractBank* loadBank(const BinaryContainer& ctr, const std::string& path); inline bool testLoadableFormat(const std::string& ext) const { return handler_.testLoadableExtension(ext); } inline bool testSavableFormat(const std::string& ext) const { return handler_.testSavableExtension(ext); } inline std::vector getLoadFilter() const { return handler_.getLoadFilterList(); } inline std::vector getSaveFilter() const { return handler_.getSaveFilterList(); } private: BankIO(); static std::unique_ptr instance_; FileIOHandlerMap handler_; }; } BambooTracker-0.6.5/BambooTracker/io/binary_container.cpp000066400000000000000000000175751476276175200234510ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "binary_container.hpp" #include #include #include #include namespace io { BinaryContainer::BinaryContainer() : isLE_(true) { } BinaryContainer::BinaryContainer(const std::vector& buf) : isLE_(true) { std::copy(buf.cbegin(), buf.cend(), std::back_inserter(buf_)); } BinaryContainer::BinaryContainer(std::vector&& buf) : isLE_(true) { std::move(buf.begin(), buf.end(), std::back_inserter(buf_)); } void BinaryContainer::clear() { buf_.clear(); buf_.shrink_to_fit(); } void BinaryContainer::appendInt8(int8_t v) { buf_.push_back(static_cast(v)); } void BinaryContainer::appendUint8(uint8_t v) { buf_.push_back(v); } void BinaryContainer::appendInt16(int16_t v) { append({ static_cast(0x00ff & v), static_cast(v >> 8), }); } void BinaryContainer::appendUint16(uint16_t v) { append({ static_cast(0x00ff & v), static_cast(v >> 8), }); } void BinaryContainer::appendInt32(int32_t v) { append({ static_cast(0x000000ff & v), static_cast(0x0000ff & (v >> 8)), static_cast(0x00ff & (v >> 16)), static_cast(v >> 24), }); } void BinaryContainer::appendUint32(uint32_t v) { append({ static_cast(0x000000ff & v), static_cast(0x0000ff & (v >> 8)), static_cast(0x00ff & (v >> 16)), static_cast(v >> 24), }); } void BinaryContainer::appendChar(char c) { buf_.push_back(static_cast(c)); } void BinaryContainer::appendString(const std::string& str) { std::copy(str.cbegin(), str.cend(), std::back_inserter(buf_)); } void BinaryContainer::appendArray(const uint8_t* array, size_type size) { std::copy(array, array + size, std::back_inserter(buf_)); } void BinaryContainer::appendVector(const std::vector& vec) { std::copy(vec.cbegin(), vec.cend(), std::back_inserter(buf_)); } void BinaryContainer::appendVector(std::vector&& vec) { std::move(vec.begin(), vec.end(), std::back_inserter(buf_)); } void BinaryContainer::appendBinaryContainer(const BinaryContainer& bc) { std::copy(bc.cbegin(), bc.cend(), std::back_inserter(buf_)); } void BinaryContainer::appendBinaryContainer(BinaryContainer&& bc) { std::move(bc.begin(), bc.end(), std::back_inserter(buf_)); } void BinaryContainer::writeInt8(size_type offset, int8_t v) { buf_.at(offset) = static_cast(v); } void BinaryContainer::writeUint8(size_type offset, uint8_t v) { buf_.at(offset) = v; } void BinaryContainer::writeInt16(size_type offset, int16_t v) { write(offset, { static_cast(0x00ff & v), static_cast(v >> 8), }); } void BinaryContainer::writeUint16(size_type offset, uint16_t v) { write(offset, { static_cast(0x00ff & v), static_cast(v >> 8), }); } void BinaryContainer::writeInt32(size_type offset, int32_t v) { write(offset, { static_cast(0x000000ff & v), static_cast(0x0000ff & (v >> 8)), static_cast(0x00ff & (v >> 16)), static_cast(v >> 24), }); } void BinaryContainer::writeUint32(size_type offset, uint32_t v) { write(offset, { static_cast(0x000000ff & v), static_cast(0x0000ff & (v >> 8)), static_cast(0x00ff & (v >> 16)), static_cast(v >> 24), }); } void BinaryContainer::writeChar(size_type offset, char c) { buf_.at(offset) = static_cast(c); } void BinaryContainer::writeString(size_type offset, const std::string& str) { if (buf_.size() <= offset || buf_.size() < offset + str.length()) throw std::out_of_range("Invalid buffer range in binary container"); std::copy(str.cbegin(), str.cend(), buf_.begin() + static_cast(offset)); } int8_t BinaryContainer::readInt8(size_type offset) const { return static_cast(buf_.at(offset)); } uint8_t BinaryContainer::readUint8(size_type offset) const { return static_cast(buf_.at(offset)); } int16_t BinaryContainer::readInt16(size_type offset) const { std::vector data = read(offset, 2); return static_cast(data[0] | (data[1] << 8)); } uint16_t BinaryContainer::readUint16(size_type offset) const { std::vector data = read(offset, 2); return static_cast(data[0] | (data[1] << 8)); } int32_t BinaryContainer::readInt32(size_type offset) const { std::vector data = read(offset, 4); return static_cast(data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)); } uint32_t BinaryContainer::readUint32(size_type offset) const { std::vector data = read(offset, 4); return static_cast(data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)); } char BinaryContainer::readChar(size_type offset) const { return buf_.at(offset); } std::string BinaryContainer::readString(size_type offset, size_type length) const { if (buf_.size() <= offset || buf_.size() < offset + length) throw std::out_of_range("Invalid buffer range in binary container"); return std::string(buf_.begin() + static_cast(offset), buf_.begin() + static_cast(offset + length)); } BinaryContainer BinaryContainer::getSubcontainer(size_type offset, size_type length) const { if (buf_.size() <= offset || buf_.size() < offset + length) throw std::out_of_range("Invalid buffer range in binary container"); BinaryContainer sub; std::copy_n(buf_.begin() + static_cast(offset), length, std::back_inserter(sub)); return sub; } std::vector BinaryContainer::toVector() const { std::vector vec; vec.reserve(buf_.size()); std::copy(buf_.cbegin(), buf_.cend(), std::back_inserter(vec)); return vec; } void BinaryContainer::append(const std::vector&& a) { if (isLE_) std::copy(a.cbegin(), a.cend(), std::back_inserter(buf_)); else std::reverse_copy(a.cbegin(), a.cend(), std::back_inserter(buf_)); } void BinaryContainer::write(size_t offset, const std::vector&& a) { if (buf_.size() <= offset || buf_.size() < offset + a.size()) throw std::out_of_range("Invalid buffer range in binary container"); if (isLE_) std::copy(a.cbegin(), a.cend(), buf_.begin() + static_cast(offset)); else std::reverse_copy(a.cbegin(), a.cend(), buf_.begin() + static_cast(offset)); } std::vector BinaryContainer::read(size_type offset, size_type size) const { if (buf_.size() <= offset || buf_.size() < offset + size) throw std::out_of_range("Invalid buffer range in binary container"); std::vector data; if (isLE_) std::copy(buf_.cbegin() + static_cast(offset), buf_.cbegin() + static_cast(offset + size), std::back_inserter(data)); else std::reverse_copy(buf_.cbegin() + static_cast(offset), buf_.cbegin() + static_cast(offset + size), std::back_inserter(data)); return data; } } BambooTracker-0.6.5/BambooTracker/io/binary_container.hpp000066400000000000000000000107611476276175200234440ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include namespace io { class BinaryContainer { public: using container_type = std::deque; using value_type = container_type::value_type; using size_type = container_type::size_type; using iterator = container_type::iterator; using const_iterator = container_type::const_iterator; using reverse_iterator = container_type::reverse_iterator; using const_reverse_iterator = container_type::const_reverse_iterator; explicit BinaryContainer(); explicit BinaryContainer(const std::vector& buf); explicit BinaryContainer(std::vector&& buf); inline iterator begin() noexcept { return buf_.begin(); } inline const_iterator begin() const noexcept { return buf_.begin(); } inline iterator end() noexcept { return buf_.end(); } inline const_iterator end() const noexcept { return buf_.end(); } inline const_iterator cbegin() const noexcept { return buf_.cbegin(); } inline const_iterator cend() const noexcept { return buf_.cend(); } inline reverse_iterator rbegin() noexcept { return buf_.rbegin(); } inline const_reverse_iterator rbegin() const noexcept { return buf_.rbegin(); } inline reverse_iterator rend() noexcept { return buf_.rend(); } inline const_reverse_iterator rend() const noexcept { return buf_.rend(); } inline const_reverse_iterator crbegin() const noexcept { return buf_.crbegin(); } inline const_reverse_iterator crend() const noexcept { return buf_.crend(); } inline void push_back(uint8_t v) { appendUint8(v); } inline size_type size() const noexcept { return buf_.size(); } void clear(); inline void resize(size_type size) { buf_.resize(size); } inline void setEndian(bool isLittleEndian) noexcept { isLE_ = isLittleEndian; } inline bool isLittleEndian() const noexcept { return isLE_; } void appendInt8(int8_t v); void appendUint8(uint8_t v); void appendInt16(int16_t v); void appendUint16(uint16_t v); void appendInt32(int32_t v); void appendUint32(uint32_t v); void appendChar(char c); void appendString(const std::string& str); void appendArray(const uint8_t* array, size_type size); void appendVector(const std::vector& vec); void appendVector(std::vector&& vec); void appendBinaryContainer(const BinaryContainer& bc); void appendBinaryContainer(BinaryContainer&& bc); void writeInt8(size_type offset, int8_t v); void writeUint8(size_type offset, uint8_t v); void writeInt16(size_type offset, int16_t v); void writeUint16(size_type offset, uint16_t v); void writeInt32(size_type offset, int32_t v); void writeUint32(size_type offset, uint32_t v); void writeChar(size_type offset, char c); void writeString(size_type offset, const std::string& str); int8_t readInt8(size_type offset) const; uint8_t readUint8(size_type offset) const; int16_t readInt16(size_type offset) const; uint16_t readUint16(size_type offset) const; int32_t readInt32(size_type offset) const; uint32_t readUint32(size_type offset) const; char readChar(size_type offset) const; std::string readString(size_type offset, size_type length) const; BinaryContainer getSubcontainer(size_type offset, size_type length) const; std::vector toVector() const; private: container_type buf_; bool isLE_; void append(const std::vector&& a); void write(size_t offset, const std::vector&& a); std::vector read(size_type offset, size_type size) const; }; } BambooTracker-0.6.5/BambooTracker/io/btb_io.cpp000066400000000000000000002513101476276175200213440ustar00rootroot00000000000000/* * Copyright (C) 2020-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "btb_io.hpp" #include #include #include #include #include #include #include "enum_hash.hpp" #include "version.hpp" #include "instrument.hpp" #include "file_io_error.hpp" #include "io_utils.hpp" namespace io { namespace { bool judgeRequiredProperty(const std::multiset& usedInstIdcs, const std::vector& reqInstIdcs) { std::vector intrsct; std::set_intersection(usedInstIdcs.begin(), usedInstIdcs.end(), reqInstIdcs.begin(), reqInstIdcs.end(), std::back_inserter(intrsct)); return !intrsct.empty(); } } BtbIO::BtbIO() : AbstractBankIO("btb", "BambooTracker bank", true, true) {} AbstractBank* BtbIO::load(const BinaryContainer& ctr) const { size_t globCsr = 0; if (ctr.readString(globCsr, 16) != "BambooTrackerBnk") throw FileCorruptionError(FileType::Bank, globCsr); globCsr += 16; /*size_t eofOfs = */ctr.readUint32(globCsr); globCsr += 4; size_t fileVersion = ctr.readUint32(globCsr); if (fileVersion > Version::ofBankFileInBCD()) throw FileVersionError(FileType::Bank); globCsr += 4; /***** Instrument section *****/ std::vector ids; std::vector names; std::vector instCtrs; if (ctr.readString(globCsr, 8) != "INSTRMNT") throw FileCorruptionError(FileType::Bank, globCsr); globCsr += 8; size_t instOfs = ctr.readUint32(globCsr); size_t instCsr = globCsr + 4; uint8_t instCnt = ctr.readUint8(instCsr); instCsr += 1; for (uint8_t i = 0; i < instCnt; ++i) { size_t pos = instCsr; uint8_t idx = ctr.readUint8(instCsr); ids.push_back(idx); instCsr += 1; size_t iOfs = ctr.readUint32(instCsr); size_t iCsr = instCsr + 4; size_t nameLen = ctr.readUint32(iCsr); iCsr += 4; std::string name = u8""; if (nameLen > 0) { name = ctr.readString(iCsr, nameLen); /* iCsr += nameLen; */ } names.push_back(name); instCsr += iOfs; // Jump to next instCtrs.push_back(ctr.getSubcontainer(pos, 1 + iOfs)); } globCsr += instOfs; /***** Instrument property section *****/ if (ctr.readString(globCsr, 8) != "INSTPROP") throw FileCorruptionError(FileType::Inst, globCsr); globCsr += 8; size_t instPropOfs = ctr.readUint32(globCsr); BinaryContainer propCtr = ctr.getSubcontainer(globCsr + 4, instPropOfs - 4); return new BtBank(ids, names, instCtrs, propCtr, fileVersion); } void BtbIO::save(BinaryContainer& ctr, const std::weak_ptr instMan, const std::vector& instNums) const { ctr.appendString("BambooTrackerBnk"); size_t eofOfs = ctr.size(); ctr.appendUint32(0); // Dummy EOF offset uint32_t fileVersion = Version::ofBankFileInBCD(); ctr.appendUint32(fileVersion); /***** Instrument section *****/ ctr.appendString("INSTRMNT"); size_t instOfs = ctr.size(); ctr.appendUint32(0); // Dummy instrument section offset ctr.appendUint8(static_cast(instNums.size())); for (auto& idx : instNums) { if (std::shared_ptr inst = instMan.lock()->getInstrumentSharedPtr(static_cast(idx))) { ctr.appendUint8(static_cast(idx)); size_t iOfs = ctr.size(); ctr.appendUint32(0); // Dummy instrument block offset std::string name = inst->getName(); ctr.appendUint32(name.length()); if (!name.empty()) ctr.appendString(name); switch (inst->getType()) { case InstrumentType::FM: { ctr.appendUint8(0x00); auto instFM = std::dynamic_pointer_cast(inst); ctr.appendUint8(static_cast(instFM->getEnvelopeNumber())); uint8_t tmp = static_cast(instFM->getLFONumber()); ctr.appendUint8(instFM->getLFOEnabled() ? tmp : (0x80 | tmp)); for (auto& param : FM_OPSEQ_PARAMS) { tmp = static_cast(instFM->getOperatorSequenceNumber(param)); ctr.appendUint8(instFM->getOperatorSequenceEnabled(param) ? tmp : (0x80 | tmp)); } tmp = static_cast(instFM->getArpeggioNumber(FMOperatorType::All)); ctr.appendUint8(instFM->getArpeggioEnabled(FMOperatorType::All) ? tmp : (0x80 | tmp)); tmp = static_cast(instFM->getPitchNumber(FMOperatorType::All)); ctr.appendUint8(instFM->getPitchEnabled(FMOperatorType::All) ? tmp : (0x80 | tmp)); tmp = static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::All)) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op1) << 1) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op2) << 2) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op3) << 3) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op4) << 4); ctr.appendUint8(tmp); for (auto& type : FM_OP_TYPES) { tmp = static_cast(instFM->getArpeggioNumber(type)); ctr.appendUint8(instFM->getArpeggioEnabled(type) ? tmp : (0x80 | tmp)); } for (auto& type : FM_OP_TYPES) { tmp = static_cast(instFM->getPitchNumber(type)); ctr.appendUint8(instFM->getPitchEnabled(type) ? tmp : (0x80 | tmp)); } tmp = static_cast(instFM->getPanNumber()); ctr.appendUint8(instFM->getPanEnabled() ? tmp : (0x80 | tmp)); break; } case InstrumentType::SSG: { ctr.appendUint8(0x01); auto instSSG = std::dynamic_pointer_cast(inst); uint8_t tmp = static_cast(instSSG->getWaveformNumber()); ctr.appendUint8(instSSG->getWaveformEnabled() ? tmp : (0x80 | tmp)); tmp = static_cast(instSSG->getToneNoiseNumber()); ctr.appendUint8(instSSG->getToneNoiseEnabled() ? tmp : (0x80 | tmp)); tmp = static_cast(instSSG->getEnvelopeNumber()); ctr.appendUint8(instSSG->getEnvelopeEnabled() ? tmp : (0x80 | tmp)); tmp = static_cast(instSSG->getArpeggioNumber()); ctr.appendUint8(instSSG->getArpeggioEnabled() ? tmp : (0x80 | tmp)); tmp = static_cast(instSSG->getPitchNumber()); ctr.appendUint8(instSSG->getPitchEnabled() ? tmp : (0x80 | tmp)); break; } case InstrumentType::ADPCM: { ctr.appendUint8(0x02); auto instADPCM = std::dynamic_pointer_cast(inst); ctr.appendUint8(static_cast(instADPCM->getSampleNumber())); uint8_t tmp = static_cast(instADPCM->getEnvelopeNumber()); ctr.appendUint8(instADPCM->getEnvelopeEnabled() ? tmp : (0x80 | tmp)); tmp = static_cast(instADPCM->getArpeggioNumber()); ctr.appendUint8(instADPCM->getArpeggioEnabled() ? tmp : (0x80 | tmp)); tmp = static_cast(instADPCM->getPitchNumber()); ctr.appendUint8(instADPCM->getPitchEnabled() ? tmp : (0x80 | tmp)); tmp = static_cast(instADPCM->getPanNumber()); ctr.appendUint8(instADPCM->getPanEnabled() ? tmp : (0x80 | tmp)); break; } case InstrumentType::Drumkit: { ctr.appendUint8(0x03); auto instKit = std::dynamic_pointer_cast(inst); std::vector keys = instKit->getAssignedKeys(); ctr.appendUint8(static_cast(keys.size())); for (const int& key : keys) { ctr.appendUint8(static_cast(key)); ctr.appendUint8(static_cast(instKit->getSampleNumber(key))); ctr.appendInt8(static_cast(instKit->getPitch(key))); ctr.appendUint8(static_cast(instKit->getPan(key))); } break; } } ctr.writeUint32(iOfs, ctr.size() - iOfs); } } ctr.writeUint32(instOfs, ctr.size() - instOfs); /***** Instrument property section *****/ ctr.appendString("INSTPROP"); size_t instPropOfs = ctr.size(); ctr.appendUint32(0); // Dummy instrument property section offset // FM envelope std::vector envFMIdcs; for (auto& idx : instMan.lock()->getEnvelopeFMEntriedIndices()) { if (judgeRequiredProperty(instMan.lock()->getEnvelopeFMUsers(idx), instNums)) envFMIdcs.push_back(idx); } if (!envFMIdcs.empty()) { ctr.appendUint8(0x00); ctr.appendUint8(static_cast(envFMIdcs.size())); for (auto& idx : envFMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint8(0); // Dummy offset uint8_t tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::AL) << 4) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::FB)); ctr.appendUint8(tmp); for (int op = 0; op < 4; ++op) { // Operator auto& params = FM_OP_PARAMS[op]; tmp = instMan.lock()->getEnvelopeFMOperatorEnabled(idx, op); tmp = static_cast((tmp << 5)) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, params.at(FMOperatorParameter::AR))); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, params.at(FMOperatorParameter::KS)) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, params.at(FMOperatorParameter::DR))); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, params.at(FMOperatorParameter::DT)) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, params.at(FMOperatorParameter::SR))); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, params.at(FMOperatorParameter::SL)) << 4) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, params.at(FMOperatorParameter::RR))); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, params.at(FMOperatorParameter::TL))); ctr.appendUint8(tmp); int tmp2 = instMan.lock()->getEnvelopeFMParameter(idx, params.at(FMOperatorParameter::SSGEG)); tmp = ((tmp2 == -1) ? 0x80 : static_cast(tmp2 << 4)) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, params.at(FMOperatorParameter::ML))); ctr.appendUint8(tmp); } ctr.writeUint8(ofs, static_cast(ctr.size() - ofs)); } } // FM LFO std::vector lfoFMIdcs; for (auto& idx : instMan.lock()->getLFOFMEntriedIndices()) { if (judgeRequiredProperty(instMan.lock()->getLFOFMUsers(idx), instNums)) lfoFMIdcs.push_back(idx); } if (!lfoFMIdcs.empty()) { ctr.appendUint8(0x01); ctr.appendUint8(static_cast(lfoFMIdcs.size())); for (auto& idx : lfoFMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint8(0); // Dummy offset uint8_t tmp = static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::FREQ) << 4) | static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::PMS)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::AM4) << 7) | static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::AM3) << 6) | static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::AM2) << 5) | static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::AM1) << 4) | static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::AMS)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::Count)); ctr.appendUint8(tmp); ctr.writeUint8(ofs, static_cast(ctr.size() - ofs)); } } // FM envelope parameter for (size_t i = 0; i < 38; ++i) { std::vector idcs; for (auto& idx : instMan.lock()->getOperatorSequenceFMEntriedIndices(FM_OPSEQ_PARAMS[i])) { if (judgeRequiredProperty(instMan.lock()->getOperatorSequenceFMUsers(FM_OPSEQ_PARAMS[i], idx), instNums)) idcs.push_back(idx); } if (!idcs.empty()) { ctr.appendUint8(0x02 + static_cast(i)); ctr.appendUint8(static_cast(idcs.size())); for (auto& idx : idcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getOperatorSequenceFMSequence(FM_OPSEQ_PARAMS[i], idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instMan.lock()->getOperatorSequenceFMLoopRoot(FM_OPSEQ_PARAMS[i], idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instMan.lock()->getOperatorSequenceFMRelease(FM_OPSEQ_PARAMS[i], idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } } // FM arpeggio std::vector arpFMIdcs; for (auto& idx : instMan.lock()->getArpeggioFMEntriedIndices()) { if (judgeRequiredProperty(instMan.lock()->getArpeggioFMUsers(idx), instNums)) arpFMIdcs.push_back(idx); } if (!arpFMIdcs.empty()) { ctr.appendUint8(0x28); ctr.appendUint8(static_cast(arpFMIdcs.size())); for (auto& idx : arpFMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getArpeggioFMSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instMan.lock()->getArpeggioFMLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instMan.lock()->getArpeggioFMRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } switch (instMan.lock()->getArpeggioFMType(idx)) { case SequenceType::AbsoluteSequence: ctr.appendUint8(0x00); break; case SequenceType::FixedSequence: ctr.appendUint8(0x01); break; case SequenceType::RelativeSequence: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // FM pitch std::vector ptFMIdcs; for (auto& idx : instMan.lock()->getPitchFMEntriedIndices()) { if (judgeRequiredProperty(instMan.lock()->getPitchFMUsers(idx), instNums)) ptFMIdcs.push_back(idx); } if (!ptFMIdcs.empty()) { ctr.appendUint8(0x29); ctr.appendUint8(static_cast(ptFMIdcs.size())); for (auto& idx : ptFMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getPitchFMSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instMan.lock()->getPitchFMLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instMan.lock()->getPitchFMRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } switch (instMan.lock()->getPitchFMType(idx)) { case SequenceType::AbsoluteSequence: ctr.appendUint8(0x00); break; case SequenceType::RelativeSequence: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // FM pan std::vector panFMIdcs; for (auto& idx : instMan.lock()->getPanFMEntriedIndices()) { if (judgeRequiredProperty(instMan.lock()->getPanFMUsers(idx), instNums)) panFMIdcs.push_back(idx); } if (!panFMIdcs.empty()) { ctr.appendUint8(0x2a); ctr.appendUint8(static_cast(panFMIdcs.size())); for (auto& idx : panFMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getPanFMSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instMan.lock()->getPanFMLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instMan.lock()->getPanFMRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // SSG waveform std::vector wfSSGIdcs; for (auto& idx : instMan.lock()->getWaveformSSGEntriedIndices()) { if (judgeRequiredProperty(instMan.lock()->getWaveformSSGUsers(idx), instNums)) wfSSGIdcs.push_back(idx); } if (!wfSSGIdcs.empty()) { ctr.appendUint8(0x30); ctr.appendUint8(static_cast(wfSSGIdcs.size())); for (auto& idx : wfSSGIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getWaveformSSGSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); ctr.appendInt32(static_cast(unit.subdata)); } auto loops = instMan.lock()->getWaveformSSGLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instMan.lock()->getWaveformSSGRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // SSG tone/noise std::vector tnSSGIdcs; for (auto& idx : instMan.lock()->getToneNoiseSSGEntriedIndices()) { if (judgeRequiredProperty(instMan.lock()->getToneNoiseSSGUsers(idx), instNums)) tnSSGIdcs.push_back(idx); } if (!tnSSGIdcs.empty()) { ctr.appendUint8(0x31); ctr.appendUint8(static_cast(tnSSGIdcs.size())); for (auto& idx : tnSSGIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getToneNoiseSSGSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instMan.lock()->getToneNoiseSSGLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instMan.lock()->getToneNoiseSSGRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // SSG envelope std::vector envSSGIdcs; for (auto& idx : instMan.lock()->getEnvelopeSSGEntriedIndices()) { if (judgeRequiredProperty(instMan.lock()->getEnvelopeSSGUsers(idx), instNums)) envSSGIdcs.push_back(idx); } if (!envSSGIdcs.empty()) { ctr.appendUint8(0x32); ctr.appendUint8(static_cast(envSSGIdcs.size())); for (auto& idx : envSSGIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getEnvelopeSSGSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); ctr.appendInt32(static_cast(unit.subdata)); } auto loops = instMan.lock()->getEnvelopeSSGLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instMan.lock()->getEnvelopeSSGRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // SSG arpeggio std::vector arpSSGIdcs; for (auto& idx : instMan.lock()->getArpeggioSSGEntriedIndices()) { if (judgeRequiredProperty(instMan.lock()->getArpeggioSSGUsers(idx), instNums)) arpSSGIdcs.push_back(idx); } if (!arpSSGIdcs.empty()) { ctr.appendUint8(0x33); ctr.appendUint8(static_cast(arpSSGIdcs.size())); for (auto& idx : arpSSGIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getArpeggioSSGSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instMan.lock()->getArpeggioSSGLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instMan.lock()->getArpeggioSSGRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } switch (instMan.lock()->getArpeggioSSGType(idx)) { case SequenceType::AbsoluteSequence: ctr.appendUint8(0x00); break; case SequenceType::FixedSequence: ctr.appendUint8(0x01); break; case SequenceType::RelativeSequence: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // SSG pitch std::vector ptSSGIdcs; for (auto& idx : instMan.lock()->getPitchSSGEntriedIndices()) { if (judgeRequiredProperty(instMan.lock()->getPitchSSGUsers(idx), instNums)) ptSSGIdcs.push_back(idx); } if (!ptSSGIdcs.empty()) { ctr.appendUint8(0x34); ctr.appendUint8(static_cast(ptSSGIdcs.size())); for (auto& idx : ptSSGIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getPitchSSGSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instMan.lock()->getPitchSSGLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instMan.lock()->getPitchSSGRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } switch (instMan.lock()->getPitchSSGType(idx)) { case SequenceType::AbsoluteSequence: ctr.appendUint8(0x00); break; case SequenceType::RelativeSequence: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // ADPCM sample std::vector sampADPCMIdcs; for (auto& idx : instMan.lock()->getSampleADPCMEntriedIndices()) { if (judgeRequiredProperty(instMan.lock()->getSampleADPCMUsers(idx), instNums)) sampADPCMIdcs.push_back(idx); } if (!sampADPCMIdcs.empty()) { ctr.appendUint8(0x40); ctr.appendUint8(static_cast(sampADPCMIdcs.size())); for (auto& idx : sampADPCMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint32(0); // Dummy offset ctr.appendUint8(static_cast(instMan.lock()->getSampleADPCMRootKeyNumber(idx))); ctr.appendUint16(static_cast(instMan.lock()->getSampleADPCMRootDeltaN(idx))); ctr.appendUint8(static_cast(instMan.lock()->isSampleADPCMRepeatable(idx))); std::vector samples = instMan.lock()->getSampleADPCMRawSample(idx); ctr.appendUint32(samples.size()); ctr.appendVector(samples); SampleRepeatRange range = instMan.lock()->getSampleADPCMRepeatRange(idx); ctr.appendUint16(range.first()); ctr.appendUint16(range.last()); ctr.writeUint32(ofs, ctr.size() - ofs); } } // ADPCM envelope std::vector envADPCMIdcs; for (auto& idx : instMan.lock()->getEnvelopeADPCMEntriedIndices()) { if (judgeRequiredProperty(instMan.lock()->getEnvelopeADPCMUsers(idx), instNums)) envADPCMIdcs.push_back(idx); } if (!envADPCMIdcs.empty()) { ctr.appendUint8(0x41); ctr.appendUint8(static_cast(envADPCMIdcs.size())); for (auto& idx : envADPCMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getEnvelopeADPCMSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instMan.lock()->getEnvelopeADPCMLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instMan.lock()->getEnvelopeADPCMRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // ADPCM arpeggio std::vector arpADPCMIdcs; for (auto& idx : instMan.lock()->getArpeggioADPCMEntriedIndices()) { if (judgeRequiredProperty(instMan.lock()->getArpeggioADPCMUsers(idx), instNums)) arpADPCMIdcs.push_back(idx); } if (!arpADPCMIdcs.empty()) { ctr.appendUint8(0x42); ctr.appendUint8(static_cast(arpADPCMIdcs.size())); for (auto& idx : arpADPCMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getArpeggioADPCMSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instMan.lock()->getArpeggioADPCMLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instMan.lock()->getArpeggioADPCMRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } switch (instMan.lock()->getArpeggioADPCMType(idx)) { case SequenceType::AbsoluteSequence: ctr.appendUint8(0x00); break; case SequenceType::FixedSequence: ctr.appendUint8(0x01); break; case SequenceType::RelativeSequence: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // ADPCM pitch std::vector ptADPCMIdcs; for (auto& idx : instMan.lock()->getPitchADPCMEntriedIndices()) { if (judgeRequiredProperty(instMan.lock()->getPitchADPCMUsers(idx), instNums)) ptADPCMIdcs.push_back(idx); } if (!ptADPCMIdcs.empty()) { ctr.appendUint8(0x43); ctr.appendUint8(static_cast(ptADPCMIdcs.size())); for (auto& idx : ptADPCMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getPitchADPCMSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instMan.lock()->getPitchADPCMLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instMan.lock()->getPitchADPCMRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } switch (instMan.lock()->getPitchADPCMType(idx)) { case SequenceType::AbsoluteSequence: ctr.appendUint8(0x00); break; case SequenceType::RelativeSequence: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // ADPCM pan std::vector panADPCMIdcs; for (auto& idx : instMan.lock()->getPanADPCMEntriedIndices()) { if (judgeRequiredProperty(instMan.lock()->getPanADPCMUsers(idx), instNums)) panADPCMIdcs.push_back(idx); } if (!panADPCMIdcs.empty()) { ctr.appendUint8(0x34); ctr.appendUint8(static_cast(panADPCMIdcs.size())); for (auto& idx : panADPCMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getPanADPCMSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instMan.lock()->getPanADPCMLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instMan.lock()->getPanADPCMRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } ctr.writeUint32(instPropOfs, ctr.size() - instPropOfs); ctr.writeUint32(eofOfs, ctr.size() - eofOfs); } namespace { size_t getPropertyPosition(const BinaryContainer& propCtr, uint8_t subsecType, uint8_t index) { size_t csr = 0; while (csr < propCtr.size()) { uint8_t type = propCtr.readUint8(csr++); bool isSection = (type == subsecType); size_t bcnt = propCtr.readUint8(csr++); for (size_t i = 0; i < bcnt; ++i) { if (isSection) { if (propCtr.readUint8(csr++) == index) { switch (type) { case 0x00: // FM envelope case 0x01: // FM LFO csr += 1; break; case 0x40: // ADPCM sample csr += 4; break; default: // Sequence csr += 2; break; } return csr; } else { switch (type) { case 0x00: // FM envelope case 0x01: // FM LFO csr += propCtr.readUint8(csr); break; case 0x40: // ADPCM sample csr += propCtr.readUint32(csr); break; default: // Sequence csr += propCtr.readUint16(csr); break; } } } else { ++csr; // Skip index switch (type) { case 0x00: // FM envelope case 0x01: // FM LFO csr += propCtr.readUint8(csr); break; case 0x40: // ADPCM sample csr += propCtr.readUint32(csr); break; default: // Sequence csr += propCtr.readUint16(csr); break; } } } } return std::numeric_limits::max(); } } AbstractInstrument* BtbIO::loadInstrument(const BinaryContainer& instCtr, const BinaryContainer& propCtr, std::weak_ptr instMan, int instNum, uint32_t bankVersion) { std::shared_ptr instManLocked = instMan.lock(); size_t instCsr = 5; // Skip instrument id and offset size_t nameLen = instCtr.readUint32(instCsr); instCsr += 4; std::string name = u8""; if (nameLen > 0) { name = instCtr.readString(instCsr, nameLen); instCsr += nameLen; } switch (instCtr.readUint8(instCsr++)) { case 0x00: // FM { auto fm = new InstrumentFM(instNum, name, instManLocked.get()); /* Envelope */ { auto orgEnvNum = instCtr.readUint8(instCsr++); int envNum = instManLocked->findFirstAssignableEnvelopeFM(); if (envNum == -1) throw FileCorruptionError(FileType::Bank, instCsr); fm->setEnvelopeNumber(envNum); size_t envCsr = getPropertyPosition(propCtr, 0x00, orgEnvNum); if (envCsr != std::numeric_limits::max()) { uint8_t tmp = propCtr.readUint8(envCsr++); instManLocked->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::AL, tmp >> 4); instManLocked->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::FB, tmp & 0x0f); for (int op = 0; op < 4; ++op) { auto& params = FM_OP_PARAMS[op]; tmp = propCtr.readUint8(envCsr++); instManLocked->setEnvelopeFMOperatorEnabled(envNum, op, (0x20 & tmp) ? true : false); instManLocked->setEnvelopeFMParameter(envNum, params.at(FMOperatorParameter::AR), tmp & 0x1f); tmp = propCtr.readUint8(envCsr++); instManLocked->setEnvelopeFMParameter(envNum, params.at(FMOperatorParameter::KS), tmp >> 5); instManLocked->setEnvelopeFMParameter(envNum, params.at(FMOperatorParameter::DR), tmp & 0x1f); tmp = propCtr.readUint8(envCsr++); instManLocked->setEnvelopeFMParameter(envNum, params.at(FMOperatorParameter::DT), tmp >> 5); instManLocked->setEnvelopeFMParameter(envNum, params.at(FMOperatorParameter::SR), tmp & 0x1f); tmp = propCtr.readUint8(envCsr++); instManLocked->setEnvelopeFMParameter(envNum, params.at(FMOperatorParameter::SL), tmp >> 4); instManLocked->setEnvelopeFMParameter(envNum, params.at(FMOperatorParameter::RR), tmp & 0x0f); tmp = propCtr.readUint8(envCsr++); instManLocked->setEnvelopeFMParameter(envNum, params.at(FMOperatorParameter::TL), tmp); tmp = propCtr.readUint8(envCsr++); instManLocked->setEnvelopeFMParameter(envNum, params.at(FMOperatorParameter::ML), tmp & 0x0f); instManLocked->setEnvelopeFMParameter(envNum, params.at(FMOperatorParameter::SSGEG), (tmp & 0x80) ? -1 : ((tmp >> 4) & 0x07)); } } } /* LFO */ { uint8_t tmp = instCtr.readUint8(instCsr++); if (0x80 & tmp) { fm->setLFOEnabled(false); fm->setLFONumber(0x7f & tmp); } else { fm->setLFOEnabled(true); uint8_t orgLFONum = 0x7f & tmp; int lfoNum = instManLocked->findFirstAssignableLFOFM(); if (lfoNum == -1) throw FileCorruptionError(FileType::Bank, instCsr); fm->setLFONumber(lfoNum); size_t lfoCsr = getPropertyPosition(propCtr, 0x01, orgLFONum); if (lfoCsr != std::numeric_limits::max()) { tmp = propCtr.readUint8(lfoCsr++); instManLocked->setLFOFMParameter(lfoNum, FMLFOParameter::FREQ, tmp >> 4); instManLocked->setLFOFMParameter(lfoNum, FMLFOParameter::PMS, tmp & 0x0f); tmp = propCtr.readUint8(lfoCsr++); instManLocked->setLFOFMParameter(lfoNum, FMLFOParameter::AMS, tmp & 0x0f); instManLocked->setLFOFMParameter(lfoNum, FMLFOParameter::AM1, (tmp & 0x10) ? true : false); instManLocked->setLFOFMParameter(lfoNum, FMLFOParameter::AM2, (tmp & 0x20) ? true : false); instManLocked->setLFOFMParameter(lfoNum, FMLFOParameter::AM3, (tmp & 0x40) ? true : false); instManLocked->setLFOFMParameter(lfoNum, FMLFOParameter::AM4, (tmp & 0x80) ? true : false); tmp = propCtr.readUint8(lfoCsr++); instManLocked->setLFOFMParameter(lfoNum, FMLFOParameter::Count, tmp); } } } /* Operator sequence */ uint8_t tmpCnt = 0; for (auto& param : FM_OPSEQ_PARAMS) { ++tmpCnt; uint8_t tmp = instCtr.readUint8(instCsr++); if (0x80 & tmp) { fm->setOperatorSequenceEnabled(param, false); fm->setOperatorSequenceNumber(param, 0x7f & tmp); } else { fm->setOperatorSequenceEnabled(param, true); uint8_t orgOpSeqNum = 0x7f & tmp; int opSeqNum = instManLocked->findFirstAssignableOperatorSequenceFM(param); if (opSeqNum == -1) throw FileCorruptionError(FileType::Bank, instCsr); fm->setOperatorSequenceNumber(param, opSeqNum); size_t opSeqCsr = getPropertyPosition(propCtr, 0x02 + tmpCnt, orgOpSeqNum); if (opSeqCsr != std::numeric_limits::max()) { uint16_t seqLen = propCtr.readUint16(opSeqCsr); opSeqCsr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = propCtr.readUint16(opSeqCsr); opSeqCsr += 2; if (l == 0) instManLocked->setOperatorSequenceFMSequenceData(param, opSeqNum, 0, data); else instManLocked->addOperatorSequenceFMSequenceData(param, opSeqNum, data); } uint16_t loopCnt = propCtr.readUint16(opSeqCsr); opSeqCsr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = propCtr.readUint16(opSeqCsr); opSeqCsr += 2; int end = propCtr.readUint16(opSeqCsr); opSeqCsr += 2; int times = propCtr.readUint8(opSeqCsr++); instManLocked->addOperatorSequenceFMLoop(param, opSeqNum, InstrumentSequenceLoop(begin, end, times)); } switch (propCtr.readUint8(opSeqCsr++)) { case 0x00: // No release instManLocked->setOperatorSequenceFMRelease(param, opSeqNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = propCtr.readUint16(opSeqCsr); opSeqCsr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setOperatorSequenceFMRelease(param, opSeqNum, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setOperatorSequenceFMRelease(param, opSeqNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Bank, instCsr); } } } } /* Arpeggio */ { std::unordered_map tmpMap; std::unordered_map orgNumMap; tmpMap.emplace(FMOperatorType::All, instCtr.readUint8(instCsr)); instCsr += 3; tmpMap.emplace(FMOperatorType::Op1, instCtr.readUint8(instCsr++)); tmpMap.emplace(FMOperatorType::Op2, instCtr.readUint8(instCsr++)); tmpMap.emplace(FMOperatorType::Op3, instCtr.readUint8(instCsr++)); tmpMap.emplace(FMOperatorType::Op4, instCtr.readUint8(instCsr)); instCsr -= 5; for (auto& pair : tmpMap) { if (0x80 & pair.second) { fm->setArpeggioEnabled(pair.first, false); fm->setArpeggioNumber(pair.first, 0x7f & pair.second); } else { fm->setArpeggioEnabled(pair.first, true); uint8_t orgArpNum = 0x7f & pair.second; auto it = orgNumMap.find(orgArpNum); if (it == orgNumMap.end()) { // Make new property orgNumMap.emplace(orgArpNum, pair.first); int arpNum = instManLocked->findFirstAssignableArpeggioFM(); if (arpNum == -1) throw FileCorruptionError(FileType::Bank, instCsr); fm->setArpeggioNumber(pair.first, arpNum); size_t arpCsr = getPropertyPosition(propCtr, 0x28, orgArpNum); if (arpCsr != std::numeric_limits::max()) { uint16_t seqLen = propCtr.readUint16(arpCsr); arpCsr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = propCtr.readUint16(arpCsr); arpCsr += 2; if (l == 0) instManLocked->setArpeggioFMSequenceData(arpNum, 0, data); else instManLocked->addArpeggioFMSequenceData(arpNum, data); } uint16_t loopCnt = propCtr.readUint16(arpCsr); arpCsr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = propCtr.readUint16(arpCsr); arpCsr += 2; int end = propCtr.readUint16(arpCsr); arpCsr += 2; int times = propCtr.readUint8(arpCsr++); instManLocked->addArpeggioFMLoop(arpNum, InstrumentSequenceLoop(begin, end, times)); } switch (propCtr.readUint8(arpCsr++)) { case 0x00: // No release instManLocked->setArpeggioFMRelease(arpNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = propCtr.readUint16(arpCsr); arpCsr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setArpeggioFMRelease(arpNum, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setArpeggioFMRelease(arpNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Bank, arpCsr); } switch (propCtr.readUint8(arpCsr++)) { case 0x00: // Absolute instManLocked->setArpeggioFMType(arpNum, SequenceType::AbsoluteSequence); break; case 0x01: // Fixed instManLocked->setArpeggioFMType(arpNum, SequenceType::FixedSequence); if (bankVersion < Version::toBCD(1, 3, 1)) { // Add infinity loop to the last data, to keep compatibility auto loopRt = instManLocked->getArpeggioFMLoopRoot(arpNum).getAllLoops(); if (std::none_of(loopRt.begin(), loopRt.end(), [](InstrumentSequenceLoop& loop) { return loop.isInfinite(); })) { auto seq = instManLocked->getArpeggioFMSequence(arpNum); if (!seq.empty()) { size_t pos = seq.size(); instManLocked->addArpeggioFMSequenceData(arpNum, seq.back().data); instManLocked->addArpeggioFMLoop(arpNum, InstrumentSequenceLoop(pos, pos, InstrumentSequenceLoop::INFINITE_LOOP)); } } } break; case 0x02: // Relative instManLocked->setArpeggioFMType(arpNum, SequenceType::RelativeSequence); break; default: if (bankVersion < Version::toBCD(1, 0, 2)) { // Recover deep clone bug // https://github.com/BambooTracker/BambooTracker/issues/170 instManLocked->setArpeggioFMType(arpNum, SequenceType::AbsoluteSequence); break; } else { throw FileCorruptionError(FileType::Bank, arpCsr); } } } } else { // Use registered property fm->setArpeggioNumber(pair.first, fm->getArpeggioNumber(it->second)); } } } } /* Pitch */ { std::unordered_map tmpMap; std::unordered_map orgNumMap; tmpMap.emplace(FMOperatorType::All, instCtr.readUint8(instCsr)); instCsr += 6; tmpMap.emplace(FMOperatorType::Op1, instCtr.readUint8(instCsr++)); tmpMap.emplace(FMOperatorType::Op2, instCtr.readUint8(instCsr++)); tmpMap.emplace(FMOperatorType::Op3, instCtr.readUint8(instCsr++)); tmpMap.emplace(FMOperatorType::Op4, instCtr.readUint8(instCsr)); instCsr -= 8; for (auto& pair : tmpMap) { if (0x80 & pair.second) { fm->setPitchEnabled(pair.first, false); fm->setPitchNumber(pair.first, 0x7f & pair.second); } else { fm->setPitchEnabled(pair.first, true); uint8_t orgPtNum = 0x7f & pair.second; auto it = orgNumMap.find(orgPtNum); if (it == orgNumMap.end()) { // Make new property orgNumMap.emplace(orgPtNum, pair.first); int ptNum = instManLocked->findFirstAssignablePitchFM(); if (ptNum == -1) throw FileCorruptionError(FileType::Bank, instCsr); fm->setPitchNumber(pair.first, ptNum); size_t ptCsr = getPropertyPosition(propCtr, 0x29, orgPtNum); if (ptCsr != std::numeric_limits::max()) { uint16_t seqLen = propCtr.readUint16(ptCsr); ptCsr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = propCtr.readUint16(ptCsr); ptCsr += 2; if (l == 0) instManLocked->setPitchFMSequenceData(ptNum, 0, data); else instManLocked->addPitchFMSequenceData(ptNum, data); } uint16_t loopCnt = propCtr.readUint16(ptCsr); ptCsr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = propCtr.readUint16(ptCsr); ptCsr += 2; int end = propCtr.readUint16(ptCsr); ptCsr += 2; int times = propCtr.readUint8(ptCsr++); instManLocked->addPitchFMLoop(ptNum, InstrumentSequenceLoop(begin, end, times)); } switch (propCtr.readUint8(ptCsr++)) { case 0x00: // No release instManLocked->setPitchFMRelease(ptNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = propCtr.readUint16(ptCsr); ptCsr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setPitchFMRelease(ptNum, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setPitchFMRelease(ptNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Bank, ptCsr); } switch (propCtr.readUint8(ptCsr++)) { case 0x00: // Absolute instManLocked->setPitchFMType(ptNum, SequenceType::AbsoluteSequence); break; case 0x02: // Relative instManLocked->setPitchFMType(ptNum, SequenceType::RelativeSequence); break; default: if (bankVersion < Version::toBCD(1, 0, 2)) { // Recover deep clone bug // https://github.com/BambooTracker/BambooTracker/issues/170 instManLocked->setPitchFMType(ptNum, SequenceType::AbsoluteSequence); break; } else { throw FileCorruptionError(FileType::Bank, ptCsr); } } } } else { // Use registered property fm->setPitchNumber(pair.first, fm->getPitchNumber(it->second)); } } } } /* Envelope reset */ { uint8_t tmp = instCtr.readUint8(instCsr); fm->setEnvelopeResetEnabled(FMOperatorType::All, (tmp & 0x01)); fm->setEnvelopeResetEnabled(FMOperatorType::Op1, (tmp & 0x02)); fm->setEnvelopeResetEnabled(FMOperatorType::Op2, (tmp & 0x04)); fm->setEnvelopeResetEnabled(FMOperatorType::Op3, (tmp & 0x08)); fm->setEnvelopeResetEnabled(FMOperatorType::Op4, (tmp & 0x10)); } /* Pan */ if (bankVersion >= Version::toBCD(1, 3, 0)) { instCsr += 9; uint8_t tmp = instCtr.readUint8(instCsr); if (0x80 & tmp) { fm->setPanEnabled(false); fm->setPanNumber(0x7f & tmp); } else { fm->setPanEnabled(true); uint8_t orgPanNum = 0x7f & tmp; int panNum = instManLocked->findFirstAssignablePanFM(); if (panNum == -1) throw FileCorruptionError(FileType::Bank, instCsr); fm->setPanNumber(panNum); size_t panCsr = getPropertyPosition(propCtr, 0x34, orgPanNum); if (panCsr != std::numeric_limits::max()) { uint16_t seqLen = propCtr.readUint16(panCsr); panCsr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = propCtr.readUint16(panCsr); panCsr += 2; if (l == 0) instManLocked->setPanFMSequenceData(panNum, 0, data); else instManLocked->addPanFMSequenceData(panNum, data); } uint16_t loopCnt = propCtr.readUint16(panCsr); panCsr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = propCtr.readUint16(panCsr); panCsr += 2; int end = propCtr.readUint16(panCsr); panCsr += 2; int times = propCtr.readUint8(panCsr++); instManLocked->addPanFMLoop(panNum, InstrumentSequenceLoop(begin, end, times)); } switch (propCtr.readUint8(panCsr++)) { case 0x00: // No release instManLocked->setPanFMRelease(panNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = propCtr.readUint16(panCsr); panCsr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setPanFMRelease(panNum, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setPanFMRelease(panNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Bank, panCsr); } } } } return fm; } case 0x01: // SSG { auto ssg = new InstrumentSSG(instNum, name, instManLocked.get()); /* Waveform */ { uint8_t tmp = instCtr.readUint8(instCsr++); if (0x80 & tmp) { ssg->setWaveformEnabled(false); ssg->setWaveformNumber(0x7f & tmp); } else { ssg->setWaveformEnabled(true); uint8_t orgWfNum = 0x7f & tmp; int wfNum = instManLocked->findFirstAssignableWaveformSSG(); if (wfNum == -1) throw FileCorruptionError(FileType::Bank, instCsr); ssg->setWaveformNumber(wfNum); size_t wfCsr = getPropertyPosition(propCtr, 0x30, orgWfNum); if (wfCsr != std::numeric_limits::max()) { uint16_t seqLen = propCtr.readUint16(wfCsr); wfCsr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = propCtr.readUint16(wfCsr); wfCsr += 2; int32_t subdata; subdata = propCtr.readInt32(wfCsr); wfCsr += 4; SSGWaveformUnit unit; switch (data) { case SSGWaveformType::SQM_TRIANGLE: case SSGWaveformType::SQM_SAW: case SSGWaveformType::SQM_INVSAW: unit = SSGWaveformUnit::makeUnitWithDecode(data, subdata); break; default: unit = SSGWaveformUnit::makeOnlyDataUnit(data); break; } if (l == 0) instManLocked->setWaveformSSGSequenceData(wfNum, 0, unit); else instManLocked->addWaveformSSGSequenceData(wfNum, unit); } uint16_t loopCnt = propCtr.readUint16(wfCsr); wfCsr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = propCtr.readUint16(wfCsr); wfCsr += 2; int end = propCtr.readUint16(wfCsr); wfCsr += 2; int times = propCtr.readUint8(wfCsr++); instManLocked->addWaveformSSGLoop(wfNum, InstrumentSequenceLoop(begin, end, times)); } switch (propCtr.readUint8(wfCsr++)) { case 0x00: // No release instManLocked->setWaveformSSGRelease(wfNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = propCtr.readUint16(wfCsr); wfCsr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setWaveformSSGRelease(wfNum, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setWaveformSSGRelease(wfNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Bank, wfCsr); } } } } /* Tone/Noise */ { uint8_t tmp = instCtr.readUint8(instCsr++); if (0x80 & tmp) { ssg->setToneNoiseEnabled(false); ssg->setToneNoiseNumber(0x7f & tmp); } else { ssg->setToneNoiseEnabled(true); uint8_t orgTnNum = 0x7f & tmp; int tnNum = instManLocked->findFirstAssignableToneNoiseSSG(); if (tnNum == -1) throw FileCorruptionError(FileType::Bank, instCsr); ssg->setToneNoiseNumber(tnNum); size_t tnCsr = getPropertyPosition(propCtr, 0x31, orgTnNum); if (tnCsr != std::numeric_limits::max()) { uint16_t seqLen = propCtr.readUint16(tnCsr); tnCsr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = propCtr.readUint16(tnCsr); tnCsr += 2; if (bankVersion < Version::toBCD(1, 0, 1)) { if (data > 0) { uint16_t tmp = data - 1; data = tmp / 32 * 32 + (31 - tmp % 32) + 1; } } if (l == 0) instManLocked->setToneNoiseSSGSequenceData(tnNum, 0, data); else instManLocked->addToneNoiseSSGSequenceData(tnNum, data); } uint16_t loopCnt = propCtr.readUint16(tnCsr); tnCsr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = propCtr.readUint16(tnCsr); tnCsr += 2; int end = propCtr.readUint16(tnCsr); tnCsr += 2; int times = propCtr.readUint8(tnCsr++); instManLocked->addToneNoiseSSGLoop(tnNum, InstrumentSequenceLoop(begin, end, times)); } switch (propCtr.readUint8(tnCsr++)) { case 0x00: // No release instManLocked->setToneNoiseSSGRelease(tnNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = propCtr.readUint16(tnCsr); tnCsr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setToneNoiseSSGRelease(tnNum, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setToneNoiseSSGRelease(tnNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Bank, tnCsr); } } } } /* Envelope */ { uint8_t tmp = instCtr.readUint8(instCsr++); if (0x80 & tmp) { ssg->setEnvelopeEnabled(false); ssg->setEnvelopeNumber(0x7f & tmp); } else { ssg->setEnvelopeEnabled(true); uint8_t orgEnvNum = 0x7f & tmp; int envNum = instManLocked->findFirstAssignableEnvelopeSSG(); if (envNum == -1) throw FileCorruptionError(FileType::Bank, instCsr); ssg->setEnvelopeNumber(envNum); size_t envCsr = getPropertyPosition(propCtr, 0x32, orgEnvNum); if (envCsr != std::numeric_limits::max()) { uint16_t seqLen = propCtr.readUint16(envCsr); envCsr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = propCtr.readUint16(envCsr); envCsr += 2; int32_t subdata; subdata = propCtr.readInt32(envCsr); envCsr += 4; SSGEnvelopeUnit unit = (data < 16) ? SSGEnvelopeUnit::makeOnlyDataUnit(data) : SSGEnvelopeUnit::makeUnitWithDecode(data, subdata); if (l == 0) instManLocked->setEnvelopeSSGSequenceData(envNum, 0, unit); else instManLocked->addEnvelopeSSGSequenceData(envNum, unit); } uint16_t loopCnt = propCtr.readUint16(envCsr); envCsr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = propCtr.readUint16(envCsr); envCsr += 2; int end = propCtr.readUint16(envCsr); envCsr += 2; int times = propCtr.readUint8(envCsr++); instManLocked->addEnvelopeSSGLoop(envNum, InstrumentSequenceLoop(begin, end, times)); } switch (propCtr.readUint8(envCsr++)) { case 0x00: // No release instManLocked->setEnvelopeSSGRelease(envNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 case 0x01: // Fixed { uint16_t pos = propCtr.readUint16(envCsr); envCsr += 2; if (pos < seqLen) instManLocked->setEnvelopeSSGRelease(envNum, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setEnvelopeSSGRelease(envNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } case 0x02: // Absolute { uint16_t pos = propCtr.readUint16(envCsr); envCsr += 2; if (pos < seqLen) instManLocked->setEnvelopeSSGRelease(envNum, InstrumentSequenceRelease(InstrumentSequenceRelease::AbsoluteRelease, pos)); else instManLocked->setEnvelopeSSGRelease(envNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } case 0x03: // Relative { uint16_t pos = propCtr.readUint16(envCsr); envCsr += 2; if (pos < seqLen) instManLocked->setEnvelopeSSGRelease(envNum, InstrumentSequenceRelease(InstrumentSequenceRelease::RelativeRelease, pos)); else instManLocked->setEnvelopeSSGRelease(envNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Bank, envCsr); } } } } /* Arpeggio */ { uint8_t tmp = instCtr.readUint8(instCsr++); if (0x80 & tmp) { ssg->setArpeggioEnabled(false); ssg->setArpeggioNumber(0x7f & tmp); } else { ssg->setArpeggioEnabled(true); uint8_t orgArpNum = 0x7f & tmp; int arpNum = instManLocked->findFirstAssignableArpeggioSSG(); if (arpNum == -1) throw FileCorruptionError(FileType::Bank, instCsr); ssg->setArpeggioNumber(arpNum); size_t arpCsr = getPropertyPosition(propCtr, 0x33, orgArpNum); if (arpCsr != std::numeric_limits::max()) { uint16_t seqLen = propCtr.readUint16(arpCsr); arpCsr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = propCtr.readUint16(arpCsr); arpCsr += 2; if (l == 0) instManLocked->setArpeggioSSGSequenceData(arpNum, 0, data); else instManLocked->addArpeggioSSGSequenceData(arpNum, data); } uint16_t loopCnt = propCtr.readUint16(arpCsr); arpCsr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = propCtr.readUint16(arpCsr); arpCsr += 2; int end = propCtr.readUint16(arpCsr); arpCsr += 2; int times = propCtr.readUint8(arpCsr++); instManLocked->addArpeggioSSGLoop(arpNum, InstrumentSequenceLoop(begin, end, times)); } switch (propCtr.readUint8(arpCsr++)) { case 0x00: // No release instManLocked->setArpeggioSSGRelease(arpNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = propCtr.readUint16(arpCsr); arpCsr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setArpeggioSSGRelease(arpNum, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setArpeggioSSGRelease(arpNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Bank, arpCsr); } switch (propCtr.readUint8(arpCsr++)) { case 0x00: // Absolute instManLocked->setArpeggioSSGType(arpNum, SequenceType::AbsoluteSequence); break; case 0x01: // Fixed instManLocked->setArpeggioSSGType(arpNum, SequenceType::FixedSequence); if (bankVersion < Version::toBCD(1, 3, 1)) { // Add infinity loop to the last data, to keep compatibility auto loopRt = instManLocked->getArpeggioSSGLoopRoot(arpNum).getAllLoops(); if (std::none_of(loopRt.begin(), loopRt.end(), [](InstrumentSequenceLoop& loop) { return loop.isInfinite(); })) { auto seq = instManLocked->getArpeggioSSGSequence(arpNum); if (!seq.empty()) { size_t pos = seq.size(); instManLocked->addArpeggioSSGSequenceData(arpNum, seq.back().data); instManLocked->addArpeggioSSGLoop(arpNum, InstrumentSequenceLoop(pos, pos, InstrumentSequenceLoop::INFINITE_LOOP)); } } } break; case 0x02: // Relative instManLocked->setArpeggioSSGType(arpNum, SequenceType::RelativeSequence); break; default: if (bankVersion < Version::toBCD(1, 0, 2)) { // Recover deep clone bug // https://github.com/BambooTracker/BambooTracker/issues/170 instManLocked->setArpeggioSSGType(arpNum, SequenceType::AbsoluteSequence); break; } else { throw FileCorruptionError(FileType::Bank, arpCsr); } } } } } /* Pitch */ { uint8_t tmp = instCtr.readUint8(instCsr++); if (0x80 & tmp) { ssg->setPitchEnabled(false); ssg->setPitchNumber(0x7f & tmp); } else { ssg->setPitchEnabled(true); uint8_t orgPtNum = 0x7f & tmp; int ptNum = instManLocked->findFirstAssignablePitchSSG(); if (ptNum == -1) throw FileCorruptionError(FileType::Bank, instCsr); ssg->setPitchNumber(ptNum); size_t ptCsr = getPropertyPosition(propCtr, 0x34, orgPtNum); if (ptCsr != std::numeric_limits::max()) { uint16_t seqLen = propCtr.readUint16(ptCsr); ptCsr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = propCtr.readUint16(ptCsr); ptCsr += 2; if (l == 0) instManLocked->setPitchSSGSequenceData(ptNum, 0, data); else instManLocked->addPitchSSGSequenceData(ptNum, data); } uint16_t loopCnt = propCtr.readUint16(ptCsr); ptCsr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = propCtr.readUint16(ptCsr); ptCsr += 2; int end = propCtr.readUint16(ptCsr); ptCsr += 2; int times = propCtr.readUint8(ptCsr++); instManLocked->addPitchSSGLoop(ptNum, InstrumentSequenceLoop(begin, end, times)); } switch (propCtr.readUint8(ptCsr++)) { case 0x00: // No release instManLocked->setPitchSSGRelease(ptNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = propCtr.readUint16(ptCsr); ptCsr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setPitchSSGRelease(ptNum, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setPitchSSGRelease(ptNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Bank, ptCsr); } switch (propCtr.readUint8(ptCsr++)) { case 0x00: // Absolute instManLocked->setPitchSSGType(ptNum, SequenceType::AbsoluteSequence); break; case 0x02: // Relative instManLocked->setPitchSSGType(ptNum, SequenceType::RelativeSequence); break; default: if (bankVersion < Version::toBCD(1, 0, 2)) { // Recover deep clone bug // https://github.com/BambooTracker/BambooTracker/issues/170 instManLocked->setPitchSSGType(ptNum, SequenceType::AbsoluteSequence); break; } else { throw FileCorruptionError(FileType::Bank, ptCsr); } } } } } return ssg; } case 0x02: // ADPCM { auto adpcm = new InstrumentADPCM(instNum, name, instManLocked.get()); /* Sample */ { uint8_t orgSampNum = instCtr.readUint8(instCsr++); int sampNum = instManLocked->findFirstAssignableSampleADPCM(); if (sampNum == -1) throw FileCorruptionError(FileType::Bank, instCsr); adpcm->setSampleNumber(sampNum); size_t sampCsr = getPropertyPosition(propCtr, 0x40, orgSampNum); if (sampCsr != std::numeric_limits::max()) { instManLocked->setSampleADPCMRootKeyNumber(sampNum, propCtr.readUint8(sampCsr++)); instManLocked->setSampleADPCMRootDeltaN(sampNum, propCtr.readUint16(sampCsr)); sampCsr += 2; instManLocked->setSampleADPCMRepeatEnabled(sampNum, (propCtr.readUint8(sampCsr++) & 0x01) != 0); uint32_t len = propCtr.readUint32(sampCsr); sampCsr += 4; std::vector samples = propCtr.getSubcontainer(sampCsr, len).toVector(); sampCsr += len; instManLocked->storeSampleADPCMRawSample(sampNum, samples); if (bankVersion >= Version::toBCD(1, 3, 1)) { uint16_t repeatBegin = propCtr.readUint16(sampCsr); sampCsr += 2; uint16_t repeatEnd = propCtr.readUint16(sampCsr); sampCsr += 2; instManLocked->setSampleADPCMRepeatrange(sampNum, SampleRepeatRange(repeatBegin, repeatEnd)); } else { instManLocked->setSampleADPCMRepeatrange(sampNum, SampleRepeatRange(0, (samples.size() - 1) >> 5)); } } } /* Envelope */ { uint8_t tmp = instCtr.readUint8(instCsr++); if (0x80 & tmp) { adpcm->setEnvelopeEnabled(false); adpcm->setEnvelopeNumber(0x7f & tmp); } else { adpcm->setEnvelopeEnabled(true); uint8_t orgEnvNum = 0x7f & tmp; int envNum = instManLocked->findFirstAssignableEnvelopeADPCM(); if (envNum == -1) throw FileCorruptionError(FileType::Bank, instCsr); adpcm->setEnvelopeNumber(envNum); size_t envCsr = getPropertyPosition(propCtr, 0x41, orgEnvNum); if (envCsr != std::numeric_limits::max()) { uint16_t seqLen = propCtr.readUint16(envCsr); envCsr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = propCtr.readUint16(envCsr); envCsr += 2; if (l == 0) instManLocked->setEnvelopeADPCMSequenceData(envNum, 0, data); else instManLocked->addEnvelopeADPCMSequenceData(envNum, data); if (bankVersion < Version::toBCD(1, 3, 0)) envCsr += 4; } uint16_t loopCnt = propCtr.readUint16(envCsr); envCsr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = propCtr.readUint16(envCsr); envCsr += 2; int end = propCtr.readUint16(envCsr); envCsr += 2; int times = propCtr.readUint8(envCsr++); instManLocked->addEnvelopeADPCMLoop(envNum, InstrumentSequenceLoop(begin, end, times)); } switch (propCtr.readUint8(envCsr++)) { case 0x00: // No release instManLocked->setEnvelopeADPCMRelease(envNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 case 0x01: // Fixed { uint16_t pos = propCtr.readUint16(envCsr); envCsr += 2; if (pos < seqLen) instManLocked->setEnvelopeADPCMRelease(envNum, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setEnvelopeADPCMRelease(envNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } case 0x02: // Absolute { uint16_t pos = propCtr.readUint16(envCsr); envCsr += 2; if (pos < seqLen) instManLocked->setEnvelopeADPCMRelease(envNum, InstrumentSequenceRelease(InstrumentSequenceRelease::AbsoluteRelease, pos)); else instManLocked->setEnvelopeADPCMRelease(envNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } case 0x03: // Relative { uint16_t pos = propCtr.readUint16(envCsr); envCsr += 2; if (pos < seqLen) instManLocked->setEnvelopeADPCMRelease(envNum, InstrumentSequenceRelease(InstrumentSequenceRelease::RelativeRelease, pos)); else instManLocked->setEnvelopeADPCMRelease(envNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Bank, envCsr); } } } } /* Arpeggio */ { uint8_t tmp = instCtr.readUint8(instCsr++); if (0x80 & tmp) { adpcm->setArpeggioEnabled(false); adpcm->setArpeggioNumber(0x7f & tmp); } else { adpcm->setArpeggioEnabled(true); uint8_t orgArpNum = 0x7f & tmp; int arpNum = instManLocked->findFirstAssignableArpeggioADPCM(); if (arpNum == -1) throw FileCorruptionError(FileType::Bank, instCsr); adpcm->setArpeggioNumber(arpNum); size_t arpCsr = getPropertyPosition(propCtr, 0x42, orgArpNum); if (arpCsr != std::numeric_limits::max()) { uint16_t seqLen = propCtr.readUint16(arpCsr); arpCsr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = propCtr.readUint16(arpCsr); arpCsr += 2; if (l == 0) instManLocked->setArpeggioADPCMSequenceData(arpNum, 0, data); else instManLocked->addArpeggioADPCMSequenceData(arpNum, data); } uint16_t loopCnt = propCtr.readUint16(arpCsr); arpCsr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = propCtr.readUint16(arpCsr); arpCsr += 2; int end = propCtr.readUint16(arpCsr); arpCsr += 2; int times = propCtr.readUint8(arpCsr++); instManLocked->addArpeggioADPCMLoop(arpNum, InstrumentSequenceLoop(begin, end, times)); } switch (propCtr.readUint8(arpCsr++)) { case 0x00: // No release instManLocked->setArpeggioADPCMRelease(arpNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = propCtr.readUint16(arpCsr); arpCsr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setArpeggioADPCMRelease(arpNum, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setArpeggioADPCMRelease(arpNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Bank, arpCsr); } switch (propCtr.readUint8(arpCsr++)) { case 0x00: // Absolute instManLocked->setArpeggioADPCMType(arpNum, SequenceType::AbsoluteSequence); break; case 0x01: // Fixed instManLocked->setArpeggioADPCMType(arpNum, SequenceType::FixedSequence); if (bankVersion < Version::toBCD(1, 3, 1)) { // Add infinity loop to the last data, to keep compatibility auto loopRt = instManLocked->getArpeggioADPCMLoopRoot(arpNum).getAllLoops(); if (std::none_of(loopRt.begin(), loopRt.end(), [](InstrumentSequenceLoop& loop) { return loop.isInfinite(); })) { auto seq = instManLocked->getArpeggioADPCMSequence(arpNum); if (!seq.empty()) { size_t pos = seq.size(); instManLocked->addArpeggioADPCMSequenceData(arpNum, seq.back().data); instManLocked->addArpeggioADPCMLoop(arpNum, InstrumentSequenceLoop(pos, pos, InstrumentSequenceLoop::INFINITE_LOOP)); } } } break; case 0x02: // Relative instManLocked->setArpeggioADPCMType(arpNum, SequenceType::RelativeSequence); break; default: throw FileCorruptionError(FileType::Bank, arpCsr); } } } } /* Pitch */ { uint8_t tmp = instCtr.readUint8(instCsr++); if (0x80 & tmp) { adpcm->setPitchEnabled(false); adpcm->setPitchNumber(0x7f & tmp); } else { adpcm->setPitchEnabled(true); uint8_t orgPtNum = 0x7f & tmp; int ptNum = instManLocked->findFirstAssignablePitchADPCM(); if (ptNum == -1) throw FileCorruptionError(FileType::Bank, instCsr); adpcm->setPitchNumber(ptNum); size_t ptCsr = getPropertyPosition(propCtr, 0x43, orgPtNum); if (ptCsr != std::numeric_limits::max()) { uint16_t seqLen = propCtr.readUint16(ptCsr); ptCsr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = propCtr.readUint16(ptCsr); ptCsr += 2; if (l == 0) instManLocked->setPitchADPCMSequenceData(ptNum, 0, data); else instManLocked->addPitchADPCMSequenceData(ptNum, data); } uint16_t loopCnt = propCtr.readUint16(ptCsr); ptCsr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = propCtr.readUint16(ptCsr); ptCsr += 2; int end = propCtr.readUint16(ptCsr); ptCsr += 2; int times = propCtr.readUint8(ptCsr++); instManLocked->addPitchADPCMLoop(ptNum, InstrumentSequenceLoop(begin, end, times)); } switch (propCtr.readUint8(ptCsr++)) { case 0x00: // No release instManLocked->setPitchADPCMRelease(ptNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = propCtr.readUint16(ptCsr); ptCsr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setPitchADPCMRelease(ptNum, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setPitchADPCMRelease(ptNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Bank, ptCsr); } switch (propCtr.readUint8(ptCsr++)) { case 0x00: // Absolute instManLocked->setPitchADPCMType(ptNum, SequenceType::AbsoluteSequence); break; case 0x02: // Relative instManLocked->setPitchADPCMType(ptNum, SequenceType::RelativeSequence); break; default: throw FileCorruptionError(FileType::Bank, ptCsr); } } } } /* Pan */ if (bankVersion >= Version::toBCD(1, 3, 0)) { uint8_t tmp = instCtr.readUint8(instCsr++); if (0x80 & tmp) { adpcm->setPanEnabled(false); adpcm->setPanNumber(0x7f & tmp); } else { adpcm->setPanEnabled(true); uint8_t orgPanNum = 0x7f & tmp; int panNum = instManLocked->findFirstAssignablePanADPCM(); if (panNum == -1) throw FileCorruptionError(FileType::Bank, instCsr); adpcm->setPanNumber(panNum); size_t panCsr = getPropertyPosition(propCtr, 0x34, orgPanNum); if (panCsr != std::numeric_limits::max()) { uint16_t seqLen = propCtr.readUint16(panCsr); panCsr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = propCtr.readUint16(panCsr); panCsr += 2; if (l == 0) instManLocked->setPanADPCMSequenceData(panNum, 0, data); else instManLocked->addPanADPCMSequenceData(panNum, data); } uint16_t loopCnt = propCtr.readUint16(panCsr); panCsr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = propCtr.readUint16(panCsr); panCsr += 2; int end = propCtr.readUint16(panCsr); panCsr += 2; int times = propCtr.readUint8(panCsr++); instManLocked->addPanADPCMLoop(panNum, InstrumentSequenceLoop(begin, end, times)); } switch (propCtr.readUint8(panCsr++)) { case 0x00: // No release instManLocked->setPanADPCMRelease(panNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = propCtr.readUint16(panCsr); panCsr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setPanADPCMRelease(panNum, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setPanADPCMRelease(panNum, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Bank, panCsr); } } } } return adpcm; } case 0x03: // Drumkit { auto kit = new InstrumentDrumkit(instNum, name, instManLocked.get()); uint8_t keyCnt = instCtr.readUint8(instCsr++); std::unordered_map sampMap; int newSamp = 0; for (uint8_t i = 0; i < keyCnt; ++i) { int key = instCtr.readUint8(instCsr++); kit->setSampleEnabled(key, true); /* Sample */ { uint8_t orgSamp = instCtr.readUint8(instCsr++); if (sampMap.count(orgSamp)) { // Use registered property kit->setSampleNumber(key, sampMap.at(orgSamp)); } else { newSamp = instManLocked->findFirstAssignableSampleADPCM(newSamp); if (newSamp == -1) throw FileCorruptionError(FileType::Bank, instCsr); kit->setSampleNumber(key, newSamp); sampMap[orgSamp] = newSamp; size_t sampCsr = getPropertyPosition(propCtr, 0x40, orgSamp); if (sampCsr != std::numeric_limits::max()) { instManLocked->setSampleADPCMRootKeyNumber(newSamp, propCtr.readUint8(sampCsr++)); instManLocked->setSampleADPCMRootDeltaN(newSamp, propCtr.readUint16(sampCsr)); sampCsr += 2; instManLocked->setSampleADPCMRepeatEnabled(newSamp, (propCtr.readUint8(sampCsr++) & 0x01) != 0); uint32_t len = propCtr.readUint32(sampCsr); sampCsr += 4; std::vector samples = propCtr.getSubcontainer(sampCsr, len).toVector(); sampCsr += len; instManLocked->storeSampleADPCMRawSample(newSamp, samples); if (bankVersion >= Version::toBCD(1, 3, 1)) { uint16_t repeatBegin = propCtr.readUint16(sampCsr); sampCsr += 2; uint16_t repeatEnd = propCtr.readUint16(sampCsr); sampCsr += 2; instManLocked->setSampleADPCMRepeatrange(newSamp, SampleRepeatRange(repeatBegin, repeatEnd)); } else { instManLocked->setSampleADPCMRepeatrange(newSamp, SampleRepeatRange(0, (samples.size() - 1) >> 5)); } ++newSamp; // Increment for search } } } /* Pitch */ kit->setPitch(key, instCtr.readInt8(instCsr++)); /* Pan */ if (bankVersion >= Version::toBCD(1, 3, 0)) { kit->setPan(key, instCtr.readUint8(instCsr++)); } } return kit; } default: throw FileCorruptionError(FileType::Bank, instCsr); } } } BambooTracker-0.6.5/BambooTracker/io/btb_io.hpp000066400000000000000000000032211476276175200213450ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "bank_io.hpp" namespace io { class BtbIO final : public AbstractBankIO { public: BtbIO(); AbstractBank* load(const BinaryContainer& ctr) const override; void save(BinaryContainer& ctr, const std::weak_ptr instMan, const std::vector& instNums) const override; static AbstractInstrument* loadInstrument(const BinaryContainer& instCtr, const BinaryContainer& propCtr, std::weak_ptr instMan, int instNum, uint32_t bankVersion); }; } BambooTracker-0.6.5/BambooTracker/io/bti_io.cpp000066400000000000000000003014231476276175200213540ustar00rootroot00000000000000/* * Copyright (C) 2020-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "bti_io.hpp" #include #include #include #include #include "enum_hash.hpp" #include "version.hpp" #include "file_io_error.hpp" #include "note.hpp" #include "io_utils.hpp" #include "utils.hpp" namespace io { namespace { size_t loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter param, size_t instMemCsr, std::shared_ptr& instManLocked, const BinaryContainer& ctr, InstrumentFM* inst, int idx, uint32_t version) { inst->setOperatorSequenceEnabled(param, true); inst->setOperatorSequenceNumber(param, idx); uint16_t ofs = ctr.readUint16(instMemCsr); size_t csr = instMemCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (version < Version::toBCD(1, 2, 1)) csr += 2; if (l == 0) instManLocked->setOperatorSequenceFMSequenceData(param, idx, 0, data); else instManLocked->addOperatorSequenceFMSequenceData(param, idx, data); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addOperatorSequenceFMLoop(param, idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setOperatorSequenceFMRelease(param, idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setOperatorSequenceFMRelease(param, idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setOperatorSequenceFMRelease(param, idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Inst, csr); } if (version >= Version::toBCD(1, 0, 1)) { ++csr; // Skip sequence type } return ofs; } } BtiIO::BtiIO() : AbstractInstrumentIO("bti", "BambooTracker instrument", true, true) {} AbstractInstrument* BtiIO::load(const BinaryContainer& ctr, const std::string& fileName, std::weak_ptr instMan, int instNum) const { (void)fileName; std::shared_ptr instManLocked = instMan.lock(); size_t globCsr = 0; if (ctr.readString(globCsr, 16) != "BambooTrackerIst") throw FileCorruptionError(FileType::Inst, globCsr); globCsr += 16; /*size_t eofOfs = */ctr.readUint32(globCsr); globCsr += 4; size_t fileVersion = ctr.readUint32(globCsr); if (fileVersion > Version::ofInstrumentFileInBCD()) throw FileVersionError(FileType::Inst); globCsr += 4; /***** Instrument section *****/ if (ctr.readString(globCsr, 8) != "INSTRMNT") throw FileCorruptionError(FileType::Inst, globCsr); else { globCsr += 8; size_t instOfs = ctr.readUint32(globCsr); size_t instCsr = globCsr + 4; size_t nameLen = ctr.readUint32(instCsr); instCsr += 4; std::string name = u8""; if (nameLen > 0) { name = ctr.readString(instCsr, nameLen); instCsr += nameLen; } std::unordered_map fmArpMap, fmPtMap; std::unordered_map> kitSampFileMap, kitSampMap; AbstractInstrument* inst = nullptr; switch (ctr.readUint8(instCsr++)) { case 0x00: // FM { inst = new InstrumentFM(instNum, name, instManLocked.get()); auto fm = dynamic_cast(inst); uint8_t tmp = ctr.readUint8(instCsr++); fm->setEnvelopeResetEnabled(FMOperatorType::All, (tmp & 0x01)); fm->setEnvelopeResetEnabled(FMOperatorType::Op1, (tmp & 0x02)); fm->setEnvelopeResetEnabled(FMOperatorType::Op2, (tmp & 0x04)); fm->setEnvelopeResetEnabled(FMOperatorType::Op3, (tmp & 0x08)); fm->setEnvelopeResetEnabled(FMOperatorType::Op4, (tmp & 0x10)); if (fileVersion >= Version::toBCD(1, 1, 0)) { fmArpMap.emplace(FMOperatorType::All, ctr.readUint8(instCsr++)); for (auto& t : FM_OP_TYPES) { tmp = ctr.readUint8(instCsr++); if (!(tmp & 0x80)) fmArpMap.emplace(t, (tmp & 0x7f)); } fmPtMap.emplace(FMOperatorType::All, ctr.readUint8(instCsr++)); for (auto& t : FM_OP_TYPES) { tmp = ctr.readUint8(instCsr++); if (!(tmp & 0x80)) fmPtMap.emplace(t, (tmp & 0x7f)); } } break; } case 0x01: // SSG { inst = new InstrumentSSG(instNum, name, instManLocked.get()); break; } case 0x02: // ADPCM { inst = new InstrumentADPCM(instNum, name, instManLocked.get()); break; } case 0x03: // Drumkit { inst = new InstrumentDrumkit(instNum, name, instManLocked.get()); auto kit = dynamic_cast(inst); uint8_t cnt = ctr.readUint8(instCsr++); for (uint8_t i = 0; i < cnt; ++i) { int key = ctr.readUint8(instCsr++); int samp = ctr.readUint8(instCsr++); if (kitSampFileMap.count(samp)) kitSampFileMap[samp].push_back(key); else kitSampFileMap[samp] = { key }; kit->setSampleEnabled(key, true); kit->setPitch(key, ctr.readInt8(instCsr++)); if (fileVersion >= Version::toBCD(1, 5, 0)) { kit->setPan(key, ctr.readUint8(instCsr++)); } } break; } default: throw FileCorruptionError(FileType::Inst, instCsr); } globCsr += instOfs; /***** Instrument property section *****/ if (ctr.readString(globCsr, 8) != "INSTPROP") throw FileCorruptionError(FileType::Inst, globCsr); else { globCsr += 8; size_t instPropOfs = ctr.readUint32(globCsr); size_t instPropCsr = globCsr + 4; size_t instPropCsrTmp = instPropCsr; globCsr += instPropOfs; int kitSampCnt = 0; int adpcmSampIdx = 0; std::vector nums; // Check memory range while (instPropCsr < globCsr) { switch (ctr.readUint8(instPropCsr++)) { case 0x00: // FM envelope { nums.push_back(instManLocked->findFirstAssignableEnvelopeFM()); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint8(instPropCsr); break; } case 0x01: // FM LFO { nums.push_back(instManLocked->findFirstAssignableLFOFM()); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint8(instPropCsr); break; } case 0x02: // FM AL { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::AL)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x03: // FM FB { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::FB)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x04: // FM AR1 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::AR1)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x05: // FM DR1 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::DR1)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x06: // FM SR1 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::SR1)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x07: // FM RR1 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::RR1)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x08: // FM SL1 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::SL1)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x09: // FM TL1 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::TL1)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x0a: // FM KS1 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::KS1)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x0b: // FM ML1 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::ML1)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x0c: // FM DT1 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::DT1)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x0d: // FM AR2 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::AR2)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x0e: // FM DR2 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::DR2)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x0f: // FM SR2 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::SR2)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x10: // FM RR2 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::RR2)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x11: // FM SL2 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::SL2)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x12: // FM TL2 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::TL2)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x13: // FM KS2 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::KS2)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x14: // FM ML2 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::ML2)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x15: // FM DT2 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::DT2)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x16: // FM AR3 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::AR3)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x17: // FM DR3 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::DR3)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x18: // FM SR3 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::SR3)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x19: // FM RR3 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::RR3)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x1a: // FM SL3 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::SL3)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x1b: // FM TL3 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::TL3)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x1c: // FM KS3 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::KS3)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x1d: // FM ML3 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::ML3)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x1e: // FM DT3 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::DT3)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x1f: // FM AR4 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::AR4)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x20: // FM DR4 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::DR4)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x21: // FM SR4 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::SR4)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x22: // FM RR4 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::RR4)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x23: // FM SL4 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::SL4)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x24: // FM TL4 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::TL4)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x25: // FM KS4 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::KS4)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x26: // FM ML4 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::ML4)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x27: // FM DT4 { nums.push_back(instManLocked->findFirstAssignableOperatorSequenceFM(FMEnvelopeParameter::DT4)); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x28: // FM arpeggio { nums.push_back(instManLocked->findFirstAssignableArpeggioFM()); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x29: // FM pitch { nums.push_back(instManLocked->findFirstAssignablePitchFM()); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x2a: // FM pan { nums.push_back(instManLocked->findFirstAssignablePanFM()); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x30: // SSG waveform { nums.push_back(instManLocked->findFirstAssignableWaveformSSG()); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x31: // SSG tone/noise { nums.push_back(instManLocked->findFirstAssignableToneNoiseSSG()); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x32: // SSG envelope { nums.push_back(instManLocked->findFirstAssignableEnvelopeSSG()); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x33: // SSG arpeggio { nums.push_back(instManLocked->findFirstAssignableArpeggioSSG()); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x34: // SSG pitch { nums.push_back(instManLocked->findFirstAssignablePitchSSG()); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x40: // ADPCM sample { adpcmSampIdx = instManLocked->findFirstAssignableSampleADPCM(adpcmSampIdx); if (adpcmSampIdx == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); nums.push_back(adpcmSampIdx); if (inst->getType() == InstrumentType::Drumkit) { kitSampMap[adpcmSampIdx] = kitSampFileMap.at(kitSampCnt++); } instPropCsr += ctr.readUint32(instPropCsr); ++adpcmSampIdx; // Increment for search appropriate kit sample break; } case 0x41: // ADPCM envelope { nums.push_back(instManLocked->findFirstAssignableEnvelopeADPCM()); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x42: // ADPCM arpeggio { nums.push_back(instManLocked->findFirstAssignableArpeggioADPCM()); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x43: // ADPCM pitch { nums.push_back(instManLocked->findFirstAssignablePitchADPCM()); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x44: // ADPCM pan { nums.push_back(instManLocked->findFirstAssignablePanADPCM()); if (nums.back() == -1) throw FileCorruptionError(FileType::Inst, instPropCsr); instPropCsr += ctr.readUint16(instPropCsr); break; } default: throw FileCorruptionError(FileType::Inst, instPropCsr); } } // Read data instPropCsr = instPropCsrTmp; auto numIt = nums.begin(); while (instPropCsr < globCsr) { switch (ctr.readUint8(instPropCsr++)) { case 0x00: // FM envelope { int idx = *numIt++; dynamic_cast(inst)->setEnvelopeNumber(idx); uint8_t ofs = ctr.readUint8(instPropCsr); size_t csr = instPropCsr + 1; uint8_t tmp = ctr.readUint8(csr++); instManLocked->setEnvelopeFMParameter(idx, FMEnvelopeParameter::AL, tmp >> 4); instManLocked->setEnvelopeFMParameter(idx, FMEnvelopeParameter::FB, tmp & 0x0f); for (int op = 0; op < 4; ++op) { auto& params = FM_OP_PARAMS[op]; tmp = ctr.readUint8(csr++); instManLocked->setEnvelopeFMOperatorEnabled(idx, op, (0x20 & tmp) ? true : false); instManLocked->setEnvelopeFMParameter(idx, params.at(FMOperatorParameter::AR), tmp & 0x1f); tmp = ctr.readUint8(csr++); instManLocked->setEnvelopeFMParameter(idx, params.at(FMOperatorParameter::KS), tmp >> 5); instManLocked->setEnvelopeFMParameter(idx, params.at(FMOperatorParameter::DR), tmp & 0x1f); tmp = ctr.readUint8(csr++); instManLocked->setEnvelopeFMParameter(idx, params.at(FMOperatorParameter::DT), tmp >> 5); instManLocked->setEnvelopeFMParameter(idx, params.at(FMOperatorParameter::SR), tmp & 0x1f); tmp = ctr.readUint8(csr++); instManLocked->setEnvelopeFMParameter(idx, params.at(FMOperatorParameter::SL), tmp >> 4); instManLocked->setEnvelopeFMParameter(idx, params.at(FMOperatorParameter::RR), tmp & 0x0f); tmp = ctr.readUint8(csr++); instManLocked->setEnvelopeFMParameter(idx, params.at(FMOperatorParameter::TL), tmp); tmp = ctr.readUint8(csr++); instManLocked->setEnvelopeFMParameter(idx, params.at(FMOperatorParameter::ML), tmp & 0x0f); instManLocked->setEnvelopeFMParameter(idx, params.at(FMOperatorParameter::SSGEG), (tmp & 0x80) ? -1 : ((tmp >> 4) & 0x07)); } instPropCsr += ofs; break; } case 0x01: // FM LFO { int idx = *numIt++; auto fm = dynamic_cast(inst); fm->setLFOEnabled(true); fm->setLFONumber(idx); uint8_t ofs = ctr.readUint8(instPropCsr); size_t csr = instPropCsr + 1; uint8_t tmp = ctr.readUint8(csr++); instManLocked->setLFOFMParameter(idx, FMLFOParameter::FREQ, tmp >> 4); instManLocked->setLFOFMParameter(idx, FMLFOParameter::PMS, tmp & 0x0f); tmp = ctr.readUint8(csr++); instManLocked->setLFOFMParameter(idx, FMLFOParameter::AMS, tmp & 0x0f); instManLocked->setLFOFMParameter(idx, FMLFOParameter::AM1, (tmp & 0x10) ? true : false); instManLocked->setLFOFMParameter(idx, FMLFOParameter::AM2, (tmp & 0x20) ? true : false); instManLocked->setLFOFMParameter(idx, FMLFOParameter::AM3, (tmp & 0x40) ? true : false); instManLocked->setLFOFMParameter(idx, FMLFOParameter::AM4, (tmp & 0x80) ? true : false); tmp = ctr.readUint8(csr++); instManLocked->setLFOFMParameter(idx, FMLFOParameter::Count, tmp); instPropCsr += ofs; break; } case 0x02: // FM AL { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::AL, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x03: // FM FB { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::FB, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x04: // FM AR1 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::AR1, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x05: // FM DR1 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::DR1, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x06: // FM SR1 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::SR1, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x07: // FM RR1 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::RR1, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x08: // FM SL1 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::SL1, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x09: // FM TL1 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::TL1, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x0a: // FM KS1 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::KS1, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x0b: // FM ML1 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::ML1, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x0c: // FM DT1 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::DT1, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x0d: // FM AR2 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::AR2, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x0e: // FM DR2 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::DR2, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x0f: // FM SR2 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::SR2, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x10: // FM RR2 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::RR2, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x11: // FM SL2 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::SL2, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x12: // FM TL2 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::TL2, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x13: // FM KS2 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::KS2, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x14: // FM ML2 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::ML2, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x15: // FM DT2 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::DT2, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x16: // FM AR3 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::AR3, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x17: // FM DR3 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::DR3, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x18: // FM SR3 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::SR3, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x19: // FM RR3 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::RR3, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x1a: // FM SL3 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::SL3, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x1b: // FM TL3 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::TL3, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x1c: // FM KS3 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::KS3, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x1d: // FM ML3 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::ML3, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x1e: // FM DT3 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::DT3, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x1f: // FM AR4 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::AR4, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x20: // FM DR4 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::DR4, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x21: // FM SR4 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::SR4, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x22: // FM RR4 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::RR4, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x23: // FM SL4 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::SL4, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x24: // FM TL4 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::TL4, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x25: // FM KS4 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::KS4, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x26: // FM ML4 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::ML4, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x27: // FM DT4 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::DT4, instPropCsr, instManLocked, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x28: // FM arpeggio { int idx = *numIt++; auto fm = dynamic_cast(inst); if (fileVersion >= Version::toBCD(1, 1, 0)) { std::vector del; for (auto& pair : fmArpMap) { if (pair.second-- == 0) { fm->setArpeggioEnabled(pair.first, true); fm->setArpeggioNumber(pair.first, idx); del.push_back(pair.first); } } for (auto t : del) fmArpMap.erase(t); } else { fm->setArpeggioEnabled(FMOperatorType::All, true); fm->setArpeggioNumber(FMOperatorType::All, idx); } uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (fileVersion < Version::toBCD(1, 2, 0)) csr += 2; if (l == 0) instManLocked->setArpeggioFMSequenceData(idx, 0, data); else instManLocked->addArpeggioFMSequenceData(idx, data); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addArpeggioFMLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setArpeggioFMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setArpeggioFMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setArpeggioFMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Inst, csr); } if (fileVersion >= Version::toBCD(1, 0, 1)) { switch (ctr.readUint8(csr++)) { case 0x00: // Absolute instManLocked->setArpeggioFMType(idx, SequenceType::AbsoluteSequence); break; case 0x01: // Fixed instManLocked->setArpeggioFMType(idx, SequenceType::FixedSequence); if (fileVersion < Version::toBCD(1, 5, 1)) { // Add infinity loop to the last data, to keep compatibility auto loopRt = instManLocked->getArpeggioFMLoopRoot(idx).getAllLoops(); if (std::none_of(loopRt.begin(), loopRt.end(), [](InstrumentSequenceLoop& loop) { return loop.isInfinite(); })) { auto seq = instManLocked->getArpeggioFMSequence(idx); if (!seq.empty()) { size_t pos = seq.size(); instManLocked->addArpeggioFMSequenceData(idx, seq.back().data); instManLocked->addArpeggioFMLoop(idx, InstrumentSequenceLoop(pos, pos, InstrumentSequenceLoop::INFINITE_LOOP)); } } } break; case 0x02: // Relative instManLocked->setArpeggioFMType(idx, SequenceType::RelativeSequence); break; default: if (fileVersion < Version::toBCD(1, 2, 3)) { // Recover deep clone bug // https://github.com/BambooTracker/BambooTracker/issues/170 instManLocked->setArpeggioFMType(idx, SequenceType::AbsoluteSequence); break; } else { throw FileCorruptionError(FileType::Inst, csr); } } } instPropCsr += ofs; break; } case 0x29: // FM pitch { int idx = *numIt++; auto fm = dynamic_cast(inst); if (fileVersion >= Version::toBCD(1, 1, 0)) { std::vector del; for (auto& pair : fmPtMap) { if (pair.second-- == 0) { fm->setPitchEnabled(pair.first, true); fm->setPitchNumber(pair.first, idx); del.push_back(pair.first); } } for (auto t : del) fmPtMap.erase(t); } else { fm->setPitchEnabled(FMOperatorType::All, true); fm->setPitchNumber(FMOperatorType::All, idx); } uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (fileVersion < Version::toBCD(1, 2, 0)) csr += 2; if (l == 0) instManLocked->setPitchFMSequenceData(idx, 0, data); else instManLocked->addPitchFMSequenceData(idx, data); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addPitchFMLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setPitchFMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setPitchFMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setPitchFMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Inst, csr); } if (fileVersion >= Version::toBCD(1, 0, 1)) { switch (ctr.readUint8(csr++)) { case 0x00: // Absolute instManLocked->setPitchFMType(idx, SequenceType::AbsoluteSequence); break; case 0x02: // Relative instManLocked->setPitchFMType(idx, SequenceType::RelativeSequence); break; default: if (fileVersion < Version::toBCD(1, 2, 3)) { // Recover deep clone bug // https://github.com/BambooTracker/BambooTracker/issues/170 instManLocked->setPitchFMType(idx, SequenceType::AbsoluteSequence); break; } else { throw FileCorruptionError(FileType::Inst, csr); } } } instPropCsr += ofs; break; } case 0x2a: // FM pan { int idx = *numIt++; auto fm = dynamic_cast(inst); fm->setPanEnabled(true); fm->setPanNumber(idx); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (l == 0) instManLocked->setPanFMSequenceData(idx, 0, data); else instManLocked->addPanFMSequenceData(idx, data); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addPanFMLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setPanFMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setPanFMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setPanFMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Inst, csr); } instPropCsr += ofs; break; } case 0x30: // SSG waveform { int idx = *numIt++; auto ssg = dynamic_cast(inst); ssg->setWaveformEnabled(true); ssg->setWaveformNumber(idx); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (fileVersion < Version::toBCD(1, 2, 0)) { if (data == 3) data = SSGWaveformType::SQM_TRIANGLE; else if (data == 4) data = SSGWaveformType::SQM_SAW; } int32_t subdata; if (fileVersion >= Version::toBCD(1, 2, 0)) { subdata = ctr.readInt32(csr); csr += 4; } else { subdata = ctr.readUint16(csr); csr += 2; if (subdata != -1) subdata = note_utils::calculateSSGSquareTP(subdata, 0); } SSGWaveformUnit unit; switch (data) { case SSGWaveformType::SQM_TRIANGLE: case SSGWaveformType::SQM_SAW: case SSGWaveformType::SQM_INVSAW: unit = SSGWaveformUnit::makeUnitWithDecode(data, subdata); break; default: unit = SSGWaveformUnit::makeOnlyDataUnit(data); break; } if (l == 0) instManLocked->setWaveformSSGSequenceData(idx, 0, unit); else instManLocked->addWaveformSSGSequenceData(idx, unit); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addWaveformSSGLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setWaveformSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setWaveformSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setWaveformSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Inst, csr); } if (fileVersion >= Version::toBCD(1, 0, 1)) { ++csr; // Skip sequence type } instPropCsr += ofs; break; } case 0x31: // SSG tone/noise { int idx = *numIt++; auto ssg = dynamic_cast(inst); ssg->setToneNoiseEnabled(true); ssg->setToneNoiseNumber(idx); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (fileVersion < Version::toBCD(1, 2, 2)) { if (data > 0) { uint16_t tmp = data - 1; data = tmp / 32 * 32 + (31 - tmp % 32) + 1; } } if (fileVersion < Version::toBCD(1, 2, 0)) csr += 2; if (l == 0) instManLocked->setToneNoiseSSGSequenceData(idx, 0, data); else instManLocked->addToneNoiseSSGSequenceData(idx, data); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addToneNoiseSSGLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setToneNoiseSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setToneNoiseSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setToneNoiseSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Inst, csr); } if (fileVersion >= Version::toBCD(1, 0, 1)) { ++csr; // Skip sequence type } instPropCsr += ofs; break; } case 0x32: // SSG envelope { int idx = *numIt++; auto ssg = dynamic_cast(inst); ssg->setEnvelopeEnabled(true); ssg->setEnvelopeNumber(idx); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; int32_t subdata; if (fileVersion >= Version::toBCD(1, 2, 0)) { subdata = ctr.readInt32(csr); csr += 4; } else { subdata = ctr.readUint16(csr); csr += 2; } SSGEnvelopeUnit unit = (data < 16) ? SSGEnvelopeUnit::makeOnlyDataUnit(data) : SSGEnvelopeUnit::makeUnitWithDecode(data, subdata); if (l == 0) instManLocked->setEnvelopeSSGSequenceData(idx, 0, unit); else instManLocked->addEnvelopeSSGSequenceData(idx, unit); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addEnvelopeSSGLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setEnvelopeSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; if (pos < seqLen) instManLocked->setEnvelopeSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setEnvelopeSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } case 0x02: // Absolute { uint16_t pos = ctr.readUint16(csr); csr += 2; if (pos < seqLen) instManLocked->setEnvelopeSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::AbsoluteRelease, pos)); else instManLocked->setEnvelopeSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } case 0x03: // Relative { uint16_t pos = ctr.readUint16(csr); csr += 2; if (pos < seqLen) instManLocked->setEnvelopeSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::RelativeRelease, pos)); else instManLocked->setEnvelopeSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Inst, csr); } if (fileVersion >= Version::toBCD(1, 0, 1)) { ++csr; // Skip sequence type } instPropCsr += ofs; break; } case 0x33: // SSG arpeggio { int idx = *numIt++; auto ssg = dynamic_cast(inst); ssg->setArpeggioEnabled(true); ssg->setArpeggioNumber(idx); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (fileVersion < Version::toBCD(1, 2, 0)) csr += 2; if (l == 0) instManLocked->setArpeggioSSGSequenceData(idx, 0, data); else instManLocked->addArpeggioSSGSequenceData(idx, data); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addArpeggioSSGLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setArpeggioSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setArpeggioSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setArpeggioSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Inst, csr); } if (fileVersion >= Version::toBCD(1, 0, 1)) { switch (ctr.readUint8(csr++)) { case 0x00: // Absolute instManLocked->setArpeggioSSGType(idx, SequenceType::AbsoluteSequence); break; case 0x01: // Fixed instManLocked->setArpeggioSSGType(idx, SequenceType::FixedSequence); if (fileVersion < Version::toBCD(1, 5, 1)) { // Add infinity loop to the last data, to keep compatibility auto loopRt = instManLocked->getArpeggioSSGLoopRoot(idx).getAllLoops(); if (std::none_of(loopRt.begin(), loopRt.end(), [](InstrumentSequenceLoop& loop) { return loop.isInfinite(); })) { auto seq = instManLocked->getArpeggioSSGSequence(idx); if (!seq.empty()) { size_t pos = seq.size(); instManLocked->addArpeggioSSGSequenceData(idx, seq.back().data); instManLocked->addArpeggioSSGLoop(idx, InstrumentSequenceLoop(pos, pos, InstrumentSequenceLoop::INFINITE_LOOP)); } } } break; case 0x02: // Relative instManLocked->setArpeggioSSGType(idx, SequenceType::RelativeSequence); break; default: if (fileVersion < Version::toBCD(1, 2, 3)) { // Recover deep clone bug // https://github.com/BambooTracker/BambooTracker/issues/170 instManLocked->setArpeggioSSGType(idx, SequenceType::AbsoluteSequence); break; } else { throw FileCorruptionError(FileType::Inst, csr); } } } instPropCsr += ofs; break; } case 0x34: // SSG pitch { int idx = *numIt++; auto ssg = dynamic_cast(inst); ssg->setPitchEnabled(true); ssg->setPitchNumber(idx); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (fileVersion < Version::toBCD(1, 2, 0)) csr += 2; if (l == 0) instManLocked->setPitchSSGSequenceData(idx, 0, data); else instManLocked->addPitchSSGSequenceData(idx, data); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addPitchSSGLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setPitchSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setPitchSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setPitchSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Inst, csr); } if (fileVersion >= Version::toBCD(1, 0, 1)) { switch (ctr.readUint8(csr++)) { case 0x00: // Absolute instManLocked->setPitchSSGType(idx, SequenceType::AbsoluteSequence); break; case 0x02: // Relative instManLocked->setPitchSSGType(idx, SequenceType::RelativeSequence); break; default: if (fileVersion < Version::toBCD(1, 2, 3)) { // Recover deep clone bug // https://github.com/BambooTracker/BambooTracker/issues/170 instManLocked->setPitchSSGType(idx, SequenceType::AbsoluteSequence); break; } else { throw FileCorruptionError(FileType::Inst, csr); } } } instPropCsr += ofs; break; } case 0x40: // ADPCM sample { int idx = *numIt++; if (inst->getType() == InstrumentType::ADPCM) { auto adpcm = dynamic_cast(inst); adpcm->setSampleNumber(idx); } else if (inst->getType() == InstrumentType::Drumkit) { auto kit = dynamic_cast(inst); for (const int& key : kitSampMap.at(idx)) kit->setSampleNumber(key, idx); } uint32_t ofs = ctr.readUint32(instPropCsr); size_t csr = instPropCsr + 4; instManLocked->setSampleADPCMRootKeyNumber(idx, ctr.readUint8(csr++)); instManLocked->setSampleADPCMRootDeltaN(idx, ctr.readUint16(csr)); csr += 2; instManLocked->setSampleADPCMRepeatEnabled(idx, (ctr.readUint8(csr++) & 0x01) != 0); uint32_t len = ctr.readUint32(csr); csr += 4; std::vector samples = ctr.getSubcontainer(csr, len).toVector(); csr += len; instManLocked->storeSampleADPCMRawSample(idx, samples); if (fileVersion >= Version::toBCD(1, 5, 1)) { uint16_t repeatBegin = ctr.readUint16(csr); csr += 2; uint16_t repeatEnd = ctr.readUint16(csr); csr += 2; instManLocked->setSampleADPCMRepeatrange(idx, SampleRepeatRange(repeatBegin, repeatEnd)); } else { instManLocked->setSampleADPCMRepeatrange(idx, SampleRepeatRange(0, (samples.size() - 1) >> 5)); } instPropCsr += ofs; break; } case 0x41: // ADPCM envelope { int idx = *numIt++; auto adpcm = dynamic_cast(inst); adpcm->setEnvelopeEnabled(true); adpcm->setEnvelopeNumber(idx); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (l == 0) instManLocked->setEnvelopeADPCMSequenceData(idx, 0, data); else instManLocked->addEnvelopeADPCMSequenceData(idx, data); if (fileVersion < Version::toBCD(1, 5, 0)) csr += 4; } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addEnvelopeADPCMLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setEnvelopeADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; if (pos < seqLen) instManLocked->setEnvelopeADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setEnvelopeADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } case 0x02: // Absolute { uint16_t pos = ctr.readUint16(csr); csr += 2; if (pos < seqLen) instManLocked->setEnvelopeADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::AbsoluteRelease, pos)); else instManLocked->setEnvelopeADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } case 0x03: // Relative { uint16_t pos = ctr.readUint16(csr); csr += 2; if (pos < seqLen) instManLocked->setEnvelopeADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::RelativeRelease, pos)); else instManLocked->setEnvelopeADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Inst, csr); } ++csr; // Skip sequence type instPropCsr += ofs; break; } case 0x42: // ADPCM arpeggio { int idx = *numIt++; auto adpcm = dynamic_cast(inst); adpcm->setArpeggioEnabled(true); adpcm->setArpeggioNumber(idx); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (fileVersion < Version::toBCD(1, 2, 0)) csr += 2; if (l == 0) instManLocked->setArpeggioADPCMSequenceData(idx, 0, data); else instManLocked->addArpeggioADPCMSequenceData(idx, data); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addArpeggioADPCMLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setArpeggioADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setArpeggioADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setArpeggioADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Inst, csr); } switch (ctr.readUint8(csr++)) { case 0x00: // Absolute instManLocked->setArpeggioADPCMType(idx, SequenceType::AbsoluteSequence); break; case 0x01: // Fixed instManLocked->setArpeggioADPCMType(idx, SequenceType::FixedSequence); if (fileVersion < Version::toBCD(1, 5, 1)) { // Add infinity loop to the last data, to keep compatibility auto loopRt = instManLocked->getArpeggioADPCMLoopRoot(idx).getAllLoops(); if (std::none_of(loopRt.begin(), loopRt.end(), [](InstrumentSequenceLoop& loop) { return loop.isInfinite(); })) { auto seq = instManLocked->getArpeggioADPCMSequence(idx); if (!seq.empty()) { size_t pos = seq.size(); instManLocked->addArpeggioADPCMSequenceData(idx, seq.back().data); instManLocked->addArpeggioADPCMLoop(idx, InstrumentSequenceLoop(pos, pos, InstrumentSequenceLoop::INFINITE_LOOP)); } } } break; case 0x02: // Relative instManLocked->setArpeggioADPCMType(idx, SequenceType::RelativeSequence); break; default: throw FileCorruptionError(FileType::Inst, csr); } instPropCsr += ofs; break; } case 0x43: // ADPCM pitch { int idx = *numIt++; auto adpcm = dynamic_cast(inst); adpcm->setPitchEnabled(true); adpcm->setPitchNumber(idx); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (fileVersion < Version::toBCD(1, 2, 0)) csr += 2; if (l == 0) instManLocked->setPitchADPCMSequenceData(idx, 0, data); else instManLocked->addPitchADPCMSequenceData(idx, data); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addPitchADPCMLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setPitchADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setPitchADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setPitchADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Inst, csr); } switch (ctr.readUint8(csr++)) { case 0x00: // Absolute instManLocked->setPitchADPCMType(idx, SequenceType::AbsoluteSequence); break; case 0x02: // Relative instManLocked->setPitchADPCMType(idx, SequenceType::RelativeSequence); break; default: throw FileCorruptionError(FileType::Inst, csr); } instPropCsr += ofs; break; } case 0x44: // ADPCM pan { int idx = *numIt++; auto adpcm = dynamic_cast(inst); adpcm->setPanEnabled(true); adpcm->setPanNumber(idx); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (l == 0) instManLocked->setPanADPCMSequenceData(idx, 0, data); else instManLocked->addPanADPCMSequenceData(idx, data); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addPanADPCMLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setPanADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setPanADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setPanADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Inst, csr); } instPropCsr += ofs; break; } default: throw FileCorruptionError(FileType::Inst, instPropCsr); } } } return inst; } } void BtiIO::save(BinaryContainer& ctr, const std::weak_ptr instMan, int instNum) const { const std::shared_ptr instManLocked = instMan.lock(); ctr.appendString("BambooTrackerIst"); size_t eofOfs = ctr.size(); ctr.appendUint32(0); // Dummy EOF offset uint32_t fileVersion = Version::ofInstrumentFileInBCD(); ctr.appendUint32(fileVersion); /***** Instrument section *****/ ctr.appendString("INSTRMNT"); size_t instOfs = ctr.size(); ctr.appendUint32(0); // Dummy instrument section offset std::vector fmArpNums, fmPtNums; std::shared_ptr inst = instManLocked->getInstrumentSharedPtr(instNum); if (inst) { std::string name = inst->getName(); ctr.appendUint32(name.length()); if (!name.empty()) ctr.appendString(name); switch (inst->getType()) { case InstrumentType::FM: { ctr.appendUint8(0x00); auto instFM = std::dynamic_pointer_cast(inst); uint8_t tmp = static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::All)) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op1) << 1) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op2) << 2) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op3) << 3) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op4) << 4); ctr.appendUint8(tmp); if (instFM->getArpeggioEnabled(FMOperatorType::All)) fmArpNums.push_back(instFM->getArpeggioNumber(FMOperatorType::All)); for (auto& t : FM_OP_TYPES) { if (instFM->getArpeggioEnabled(t)) { int n = instFM->getArpeggioNumber(t); if (utils::find(fmArpNums, n) == fmArpNums.end()) fmArpNums.push_back(n); } } if (instFM->getArpeggioEnabled(FMOperatorType::All)) { int n = instFM->getArpeggioNumber(FMOperatorType::All); for (size_t i = 0; i < fmArpNums.size(); ++i) { if (n == fmArpNums[i]) { ctr.appendUint8(static_cast(i)); break; } } } else { ctr.appendUint8(0x80); } for (auto& t : FM_OP_TYPES) { if (instFM->getArpeggioEnabled(t)) { int n = instFM->getArpeggioNumber(t); for (size_t i = 0; i < fmArpNums.size(); ++i) { if (n == fmArpNums[i]) { ctr.appendUint8(static_cast(i)); break; } } } else { ctr.appendUint8(0x80); } } if (instFM->getPitchEnabled(FMOperatorType::All)) fmPtNums.push_back(instFM->getPitchNumber(FMOperatorType::All)); for (auto& t : FM_OP_TYPES) { if (instFM->getPitchEnabled(t)) { int n = instFM->getPitchNumber(t); if (utils::find(fmPtNums, n) == fmPtNums.end()) fmPtNums.push_back(n); } } if (instFM->getPitchEnabled(FMOperatorType::All)) { int n = instFM->getPitchNumber(FMOperatorType::All); for (size_t i = 0; i < fmPtNums.size(); ++i) { if (n == fmPtNums[i]) { ctr.appendUint8(static_cast(i)); break; } } } else { ctr.appendUint8(0x80); } for (auto& t : FM_OP_TYPES) { if (instFM->getPitchEnabled(t)) { int n = instFM->getPitchNumber(t); for (size_t i = 0; i < fmPtNums.size(); ++i) { if (n == fmPtNums[i]) { ctr.appendUint8(static_cast(i)); break; } } } else { ctr.appendUint8(0x80); } } break; } case InstrumentType::SSG: { ctr.appendUint8(0x01); break; } case InstrumentType::ADPCM: { ctr.appendUint8(0x02); break; } case InstrumentType::Drumkit: { ctr.appendUint8(0x03); auto kit = std::dynamic_pointer_cast(inst); std::vector keys = kit->getAssignedKeys(); ctr.appendUint8(static_cast(keys.size())); int sampCnt = 0; std::unordered_map sampMap; for (const int& key : keys) { ctr.appendUint8(static_cast(key)); int samp = kit->getSampleNumber(key); if (!sampMap.count(samp)) sampMap[samp] = sampCnt++; ctr.appendUint8(static_cast(sampMap[samp])); ctr.appendInt8(static_cast(kit->getPitch(key))); ctr.appendUint8(static_cast(kit->getPan(key))); } break; } } } ctr.writeUint32(instOfs, ctr.size() - instOfs); /***** Instrument property section *****/ ctr.appendString("INSTPROP"); size_t instPropOfs = ctr.size(); ctr.appendUint32(0); // Dummy instrument property section offset switch (inst->getType()) { case InstrumentType::FM: { auto instFM = std::dynamic_pointer_cast(inst); // FM envelope int envNum = instFM->getEnvelopeNumber(); { ctr.appendUint8(0x00); size_t ofs = ctr.size(); ctr.appendUint8(0); // Dummy offset uint8_t tmp = static_cast(instManLocked->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::AL) << 4) | static_cast(instManLocked->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::FB)); ctr.appendUint8(tmp); for (int op = 0; op < 4; ++op) { auto& params = FM_OP_PARAMS[op]; tmp = instManLocked->getEnvelopeFMOperatorEnabled(envNum, op); tmp = static_cast(tmp << 5) | static_cast(instManLocked->getEnvelopeFMParameter(envNum, params.at(FMOperatorParameter::AR))); ctr.appendUint8(tmp); tmp = static_cast(instManLocked->getEnvelopeFMParameter(envNum, params.at(FMOperatorParameter::KS)) << 5) | static_cast(instManLocked->getEnvelopeFMParameter(envNum, params.at(FMOperatorParameter::DR))); ctr.appendUint8(tmp); tmp = static_cast(instManLocked->getEnvelopeFMParameter(envNum, params.at(FMOperatorParameter::DT)) << 5) | static_cast(instManLocked->getEnvelopeFMParameter(envNum, params.at(FMOperatorParameter::SR))); ctr.appendUint8(tmp); tmp = static_cast(instManLocked->getEnvelopeFMParameter(envNum, params.at(FMOperatorParameter::SL)) << 4) | static_cast(instManLocked->getEnvelopeFMParameter(envNum, params.at(FMOperatorParameter::RR))); ctr.appendUint8(tmp); tmp = static_cast(instManLocked->getEnvelopeFMParameter(envNum, params.at(FMOperatorParameter::TL))); ctr.appendUint8(tmp); int tmp2 = instManLocked->getEnvelopeFMParameter(envNum, params.at(FMOperatorParameter::SSGEG)); tmp = ((tmp2 == -1) ? 0x80 : static_cast(tmp2 << 4)) | static_cast(instManLocked->getEnvelopeFMParameter(envNum, params.at(FMOperatorParameter::ML))); ctr.appendUint8(tmp); } ctr.writeUint8(ofs, static_cast(ctr.size() - ofs)); } // FM LFO if (instFM->getLFOEnabled()) { int lfoNum = instFM->getLFONumber(); ctr.appendUint8(0x01); size_t ofs = ctr.size(); ctr.appendUint8(0); // Dummy offset uint8_t tmp = static_cast(instManLocked->getLFOFMparameter(lfoNum, FMLFOParameter::FREQ) << 4) | static_cast(instManLocked->getLFOFMparameter(lfoNum, FMLFOParameter::PMS)); ctr.appendUint8(tmp); tmp = static_cast(instManLocked->getLFOFMparameter(lfoNum, FMLFOParameter::AM4) << 7) | static_cast(instManLocked->getLFOFMparameter(lfoNum, FMLFOParameter::AM3) << 6) | static_cast(instManLocked->getLFOFMparameter(lfoNum, FMLFOParameter::AM2) << 5) | static_cast(instManLocked->getLFOFMparameter(lfoNum, FMLFOParameter::AM1) << 4) | static_cast(instManLocked->getLFOFMparameter(lfoNum, FMLFOParameter::AMS)); ctr.appendUint8(tmp); tmp = static_cast(instManLocked->getLFOFMparameter(lfoNum, FMLFOParameter::Count)); ctr.appendUint8(tmp); ctr.writeUint8(ofs, static_cast(ctr.size() - ofs)); } // FM envelope parameter for (size_t i = 0; i < 38; ++i) { if (instFM->getOperatorSequenceEnabled(FM_OPSEQ_PARAMS[i])) { int seqNum = instFM->getOperatorSequenceNumber(FM_OPSEQ_PARAMS[i]); ctr.appendUint8(0x02 + static_cast(i)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getOperatorSequenceFMSequence(FM_OPSEQ_PARAMS[i], seqNum); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instManLocked->getOperatorSequenceFMLoopRoot(FM_OPSEQ_PARAMS[i], seqNum).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getOperatorSequenceFMRelease(FM_OPSEQ_PARAMS[i], seqNum); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // FM arpeggio for (int& arpNum : fmArpNums) { ctr.appendUint8(0x28); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getArpeggioFMSequence(arpNum); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instManLocked->getArpeggioFMLoopRoot(arpNum).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getArpeggioFMRelease(arpNum); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } switch (instManLocked->getArpeggioFMType(arpNum)) { case SequenceType::AbsoluteSequence: ctr.appendUint8(0x00); break; case SequenceType::FixedSequence: ctr.appendUint8(0x01); break; case SequenceType::RelativeSequence: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } // FM pitch for (int& ptNum : fmPtNums) { ctr.appendUint8(0x29); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getPitchFMSequence(ptNum); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instManLocked->getPitchFMLoopRoot(ptNum).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getPitchFMRelease(ptNum); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } switch (instManLocked->getPitchFMType(ptNum)) { case SequenceType::AbsoluteSequence: ctr.appendUint8(0x00); break; case SequenceType::RelativeSequence: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } // FM pan if (instFM->getPanEnabled()) { int panNum = instFM->getPanNumber(); ctr.appendUint8(0x2a); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getPanFMSequence(panNum); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instManLocked->getPanFMLoopRoot(panNum).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getPanFMRelease(panNum); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } break; } case InstrumentType::SSG: { auto instSSG = std::dynamic_pointer_cast(inst); // SSG waveform if (instSSG->getWaveformEnabled()) { int wfNum = instSSG->getWaveformNumber(); ctr.appendUint8(0x30); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getWaveformSSGSequence(wfNum); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); ctr.appendInt32(static_cast(unit.subdata)); } auto loops = instManLocked->getWaveformSSGLoopRoot(wfNum).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getWaveformSSGRelease(wfNum); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } // SSG tone/noise if (instSSG->getToneNoiseEnabled()) { int tnNum = instSSG->getToneNoiseNumber(); ctr.appendUint8(0x31); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getToneNoiseSSGSequence(tnNum); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instManLocked->getToneNoiseSSGLoopRoot(tnNum).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getToneNoiseSSGRelease(tnNum); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } // SSG envelope if (instSSG->getEnvelopeEnabled()) { int envNum = instSSG->getEnvelopeNumber(); ctr.appendUint8(0x32); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getEnvelopeSSGSequence(envNum); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); ctr.appendInt32(static_cast(unit.subdata)); } auto loops = instManLocked->getEnvelopeSSGLoopRoot(envNum).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getEnvelopeSSGRelease(envNum); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } // SSG arpeggio if (instSSG->getArpeggioEnabled()) { int arpNum = instSSG->getArpeggioNumber(); ctr.appendUint8(0x33); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getArpeggioSSGSequence(arpNum); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instManLocked->getArpeggioSSGLoopRoot(arpNum).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getArpeggioSSGRelease(arpNum); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } switch (instManLocked->getArpeggioSSGType(arpNum)) { case SequenceType::AbsoluteSequence: ctr.appendUint8(0x00); break; case SequenceType::FixedSequence: ctr.appendUint8(0x01); break; case SequenceType::RelativeSequence: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } // SSG pitch if (instSSG->getPitchEnabled()) { int ptNum = instSSG->getPitchNumber(); ctr.appendUint8(0x34); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getPitchSSGSequence(ptNum); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instManLocked->getPitchSSGLoopRoot(ptNum).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getPitchSSGRelease(ptNum); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } switch (instManLocked->getPitchSSGType(ptNum)) { case SequenceType::AbsoluteSequence: ctr.appendUint8(0x00); break; case SequenceType::RelativeSequence: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } break; } case InstrumentType::ADPCM: { auto instADPCM = std::dynamic_pointer_cast(inst); // ADPCM sample int sampNum = instADPCM->getSampleNumber(); { ctr.appendUint8(0x40); size_t ofs = ctr.size(); ctr.appendUint32(0); // Dummy offset ctr.appendUint8(static_cast(instManLocked->getSampleADPCMRootKeyNumber(sampNum))); ctr.appendUint16(static_cast(instManLocked->getSampleADPCMRootDeltaN(sampNum))); ctr.appendUint8(static_cast(instManLocked->isSampleADPCMRepeatable(sampNum))); std::vector samples = instManLocked->getSampleADPCMRawSample(sampNum); ctr.appendUint32(samples.size()); ctr.appendVector(std::move(samples)); SampleRepeatRange range = instMan.lock()->getSampleADPCMRepeatRange(sampNum); ctr.appendUint16(range.first()); ctr.appendUint16(range.last()); ctr.writeUint32(ofs, ctr.size() - ofs); } // ADPCM envelope if (instADPCM->getEnvelopeEnabled()) { int envNum = instADPCM->getEnvelopeNumber(); ctr.appendUint8(0x41); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getEnvelopeADPCMSequence(envNum); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instManLocked->getEnvelopeADPCMLoopRoot(envNum).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getEnvelopeADPCMRelease(envNum); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } // ADPCM arpeggio if (instADPCM->getArpeggioEnabled()) { int arpNum = instADPCM->getArpeggioNumber(); ctr.appendUint8(0x42); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getArpeggioADPCMSequence(arpNum); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instManLocked->getArpeggioADPCMLoopRoot(arpNum).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getArpeggioADPCMRelease(arpNum); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } switch (instManLocked->getArpeggioADPCMType(arpNum)) { case SequenceType::AbsoluteSequence: ctr.appendUint8(0x00); break; case SequenceType::FixedSequence: ctr.appendUint8(0x01); break; case SequenceType::RelativeSequence: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } // ADPCM pitch if (instADPCM->getPitchEnabled()) { int ptNum = instADPCM->getPitchNumber(); ctr.appendUint8(0x43); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getPitchADPCMSequence(ptNum); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instManLocked->getPitchADPCMLoopRoot(ptNum).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getPitchADPCMRelease(ptNum); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } switch (instManLocked->getPitchADPCMType(ptNum)) { case SequenceType::AbsoluteSequence: ctr.appendUint8(0x00); break; case SequenceType::RelativeSequence: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } // ADPCM pan if (instADPCM->getPanEnabled()) { int panNum = instADPCM->getPanNumber(); ctr.appendUint8(0x44); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getPanADPCMSequence(panNum); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instManLocked->getPanADPCMLoopRoot(panNum).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getPanADPCMRelease(panNum); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } break; } case InstrumentType::Drumkit: { auto kit = std::dynamic_pointer_cast(inst); // ADPCM sample { std::vector sampList; for (const int& key : kit->getAssignedKeys()) { int samp = kit->getSampleNumber(key); if (std::none_of(sampList.begin(), sampList.end(), [samp](const int& v) { return v == samp; })) { sampList.push_back(samp); ctr.appendUint8(0x40); size_t ofs = ctr.size(); ctr.appendUint32(0); // Dummy offset ctr.appendUint8(static_cast(instManLocked->getSampleADPCMRootKeyNumber(samp))); ctr.appendUint16(static_cast(instManLocked->getSampleADPCMRootDeltaN(samp))); ctr.appendUint8(static_cast(instManLocked->isSampleADPCMRepeatable(samp))); std::vector samples = instManLocked->getSampleADPCMRawSample(samp); ctr.appendUint32(samples.size()); ctr.appendVector(std::move(samples)); SampleRepeatRange range = instMan.lock()->getSampleADPCMRepeatRange(samp); ctr.appendUint16(range.first()); ctr.appendUint16(range.last()); ctr.writeUint32(ofs, ctr.size() - ofs); } } } break; } } ctr.writeUint32(instPropOfs, ctr.size() - instPropOfs); ctr.writeUint32(eofOfs, ctr.size() - eofOfs); } } BambooTracker-0.6.5/BambooTracker/io/bti_io.hpp000066400000000000000000000027741476276175200213700ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "instrument_io.hpp" namespace io { class BtiIO final : public AbstractInstrumentIO { public: BtiIO(); AbstractInstrument* load(const BinaryContainer& ctr, const std::string& fileName, std::weak_ptr instMan, int instNum) const override; void save(BinaryContainer& ctr, const std::weak_ptr instMan, int instNum) const override; }; } BambooTracker-0.6.5/BambooTracker/io/btm_io.cpp000066400000000000000000003066201476276175200213640ustar00rootroot00000000000000/* * Copyright (C) 2020-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "btm_io.hpp" #include "file_io_error.hpp" #include "version.hpp" #include "note.hpp" #include "effect.hpp" #include "io_utils.hpp" namespace io { namespace { size_t loadModuleSection(std::weak_ptr mod, const BinaryContainer& ctr, size_t globCsr, uint32_t version) { std::shared_ptr modLocked = mod.lock(); size_t modOfs = ctr.readUint32(globCsr); size_t modCsr = globCsr + 4; size_t modTitleLen = ctr.readUint32(modCsr); modCsr += 4; if (modTitleLen > 0) { modLocked->setTitle(ctr.readString(modCsr, modTitleLen)); modCsr += modTitleLen; } size_t modAuthorLen = ctr.readUint32(modCsr); modCsr += 4; if (modAuthorLen > 0) { modLocked->setAuthor(ctr.readString(modCsr, modAuthorLen)); modCsr += modAuthorLen; } size_t modCopyrightLen = ctr.readUint32(modCsr); modCsr += 4; if (modCopyrightLen > 0) { modLocked->setCopyright(ctr.readString(modCsr, modCopyrightLen)); modCsr += modCopyrightLen; } size_t modCommentLen = ctr.readUint32(modCsr); modCsr += 4; if (modCommentLen > 0) { modLocked->setComment(ctr.readString(modCsr, modCommentLen)); modCsr += modCommentLen; } modLocked->setTickFrequency(ctr.readUint32(modCsr)); modCsr += 4; modLocked->setStepHighlight1Distance(ctr.readUint32(modCsr)); modCsr += 4; if (version >= Version::toBCD(1, 0, 3)) { modLocked->setStepHighlight2Distance(ctr.readUint32(modCsr)); modCsr += 4; } else { modLocked->setStepHighlight2Distance(modLocked->getStepHighlight1Distance() * 4); } if (version >= Version::toBCD(1, 3, 0)) { auto mixType = static_cast(ctr.readUint8(modCsr++)); modLocked->setMixerType(mixType); if (mixType == MixerType::CUSTOM) { modLocked->setCustomMixerFMLevel(ctr.readInt8(modCsr++) / 10.0); modLocked->setCustomMixerSSGLevel(ctr.readInt8(modCsr++) / 10.0); } } else { modLocked->setMixerType(MixerType::UNSPECIFIED); } return globCsr + modOfs; } size_t loadInstrumentSection(std::weak_ptr instMan, const BinaryContainer& ctr, size_t globCsr, uint32_t version) { std::shared_ptr instManLocked = instMan.lock(); size_t instOfs = ctr.readUint32(globCsr); size_t instCsr = globCsr + 4; uint8_t instCnt = ctr.readUint8(instCsr); instCsr += 1; for (uint8_t i = 0; i < instCnt; ++i) { uint8_t idx = ctr.readUint8(instCsr); instCsr += 1; size_t iOfs = ctr.readUint32(instCsr); size_t iCsr = instCsr + 4; size_t nameLen = ctr.readUint32(iCsr); iCsr += 4; std::string name = u8""; if (nameLen > 0) { name = ctr.readString(iCsr, nameLen); iCsr += nameLen; } switch (ctr.readUint8(iCsr++)) { case 0x00: // FM { auto instFM = new InstrumentFM(idx, name, instManLocked.get()); instFM->setEnvelopeNumber(ctr.readUint8(iCsr)); iCsr += 1; uint8_t tmp = ctr.readUint8(iCsr); instFM->setLFOEnabled((0x80 & tmp) ? false : true); instFM->setLFONumber(0x7f & tmp); iCsr += 1; for (auto& param : FM_OPSEQ_PARAMS) { tmp = ctr.readUint8(iCsr); instFM->setOperatorSequenceEnabled(param, (0x80 & tmp) ? false : true); instFM->setOperatorSequenceNumber(param, 0x7f & tmp); iCsr += 1; } tmp = ctr.readUint8(iCsr); instFM->setArpeggioEnabled(FMOperatorType::All, (0x80 & tmp) ? false : true); instFM->setArpeggioNumber(FMOperatorType::All, 0x7f & tmp); iCsr += 1; tmp = ctr.readUint8(iCsr); instFM->setPitchEnabled(FMOperatorType::All, (0x80 & tmp) ? false : true); instFM->setPitchNumber(FMOperatorType::All, 0x7f & tmp); iCsr += 1; tmp = ctr.readUint8(iCsr); instFM->setEnvelopeResetEnabled(FMOperatorType::All, (tmp & 0x01)); instFM->setEnvelopeResetEnabled(FMOperatorType::Op1, (tmp & 0x02)); instFM->setEnvelopeResetEnabled(FMOperatorType::Op2, (tmp & 0x04)); instFM->setEnvelopeResetEnabled(FMOperatorType::Op3, (tmp & 0x08)); instFM->setEnvelopeResetEnabled(FMOperatorType::Op4, (tmp & 0x10)); iCsr += 1; if (version >= Version::toBCD(1, 1, 0)) { for (auto& t : FM_OP_TYPES) { tmp = ctr.readUint8(iCsr); instFM->setArpeggioEnabled(t, (0x80 & tmp) ? false : true); instFM->setArpeggioNumber(t, 0x7f & tmp); iCsr += 1; } for (auto& t : FM_OP_TYPES) { tmp = ctr.readUint8(iCsr); instFM->setPitchEnabled(t, (0x80 & tmp) ? false : true); instFM->setPitchNumber(t, 0x7f & tmp); iCsr += 1; } } tmp = ctr.readUint8(iCsr); if (version >= Version::toBCD(1, 6, 0)) { instFM->setPanEnabled((0x80 & tmp) ? false : true); instFM->setPanNumber(0x7f & tmp); /* iCsr += 1; */ } instManLocked->addInstrument(instFM); break; } case 0x01: // SSG { auto instSSG = new InstrumentSSG(idx, name, instManLocked.get()); uint8_t tmp = ctr.readUint8(iCsr); instSSG->setWaveformEnabled((0x80 & tmp) ? false : true); instSSG->setWaveformNumber(0x7f & tmp); iCsr += 1; tmp = ctr.readUint8(iCsr); instSSG->setToneNoiseEnabled((0x80 & tmp) ? false : true); instSSG->setToneNoiseNumber(0x7f & tmp); iCsr += 1; tmp = ctr.readUint8(iCsr); instSSG->setEnvelopeEnabled((0x80 & tmp) ? false : true); instSSG->setEnvelopeNumber(0x7f & tmp); iCsr += 1; tmp = ctr.readUint8(iCsr); instSSG->setArpeggioEnabled((0x80 & tmp) ? false : true); instSSG->setArpeggioNumber(0x7f & tmp); iCsr += 1; tmp = ctr.readUint8(iCsr); instSSG->setPitchEnabled((0x80 & tmp) ? false : true); instSSG->setPitchNumber(0x7f & tmp); /* iCsr += 1; */ instManLocked->addInstrument(instSSG); break; } case 0x02: // ADPCM { auto instADPCM = new InstrumentADPCM(idx, name, instManLocked.get()); instADPCM->setSampleNumber(ctr.readUint8(iCsr++)); uint8_t tmp = ctr.readUint8(iCsr); instADPCM->setEnvelopeEnabled((0x80 & tmp) ? false : true); instADPCM->setEnvelopeNumber(0x7f & tmp); iCsr += 1; tmp = ctr.readUint8(iCsr); instADPCM->setArpeggioEnabled((0x80 & tmp) ? false : true); instADPCM->setArpeggioNumber(0x7f & tmp); iCsr += 1; tmp = ctr.readUint8(iCsr); instADPCM->setPitchEnabled((0x80 & tmp) ? false : true); instADPCM->setPitchNumber(0x7f & tmp); iCsr += 1; if (version >= Version::toBCD(1, 6, 0)) { tmp = ctr.readUint8(iCsr); instADPCM->setPanEnabled((0x80 & tmp) ? false : true); instADPCM->setPanNumber(0x7f & tmp); /* iCsr += 1; */ } instManLocked->addInstrument(instADPCM); break; } case 0x03: // Drumkit { auto instKit = new InstrumentDrumkit(idx, name, instManLocked.get()); int cnt = ctr.readUint8(iCsr++); for (int i = 0; i < cnt; ++i) { int key = ctr.readUint8(iCsr++); instKit->setSampleEnabled(key, true); instKit->setSampleNumber(key, ctr.readUint8(iCsr++)); instKit->setPitch(key, ctr.readInt8(iCsr++)); if (version >= Version::toBCD(1, 6, 0)) { instKit->setPan(key, ctr.readUint8(iCsr++)); } } instManLocked->addInstrument(instKit); break; } default: throw FileCorruptionError(FileType::Mod, iCsr); } instCsr += iOfs; } return globCsr + instOfs; } size_t loadOperatorSequence(FMEnvelopeParameter param, size_t instMemCsr, std::shared_ptr& instManLocked, const BinaryContainer& ctr, uint32_t version) { uint8_t idx = ctr.readUint8(instMemCsr++); uint16_t ofs = ctr.readUint16(instMemCsr); size_t csr = instMemCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (version < Version::toBCD(1, 2, 2)) csr += 2; if (l == 0) instManLocked->setOperatorSequenceFMSequenceData(param, idx, 0, data); else instManLocked->addOperatorSequenceFMSequenceData(param, idx, data); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addOperatorSequenceFMLoop(param, idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setOperatorSequenceFMRelease(param, idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug; see BambooTracker/BambooTracker issue #11) if (pos < seqLen) instManLocked->setOperatorSequenceFMRelease(param, idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setOperatorSequenceFMRelease(param, idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Mod, csr); } if (version >= Version::toBCD(1, 0, 1)) { ++csr; // Skip sequence type } return ofs + 1; } size_t loadInstrumentPropertySection(std::weak_ptr instMan, const BinaryContainer& ctr, size_t globCsr, uint32_t version) { std::shared_ptr instManLocked = instMan.lock(); size_t instPropOfs = ctr.readUint32(globCsr); size_t instPropCsr = globCsr + 4; globCsr += instPropOfs; while (instPropCsr < globCsr) { switch (ctr.readUint8(instPropCsr++)) { case 0x00: // FM envelope { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint8_t ofs = ctr.readUint8(instPropCsr); size_t csr = instPropCsr + 1; uint8_t tmp = ctr.readUint8(csr++); instManLocked->setEnvelopeFMParameter(idx, FMEnvelopeParameter::AL, tmp >> 4); instManLocked->setEnvelopeFMParameter(idx, FMEnvelopeParameter::FB, tmp & 0x0f); for (int op = 0; op < 4; ++op) { auto& params = FM_OP_PARAMS[op]; tmp = ctr.readUint8(csr++); instManLocked->setEnvelopeFMOperatorEnabled(idx, op, (0x20 & tmp) ? true : false); instManLocked->setEnvelopeFMParameter(idx, params.at(FMOperatorParameter::AR), tmp & 0x1f); tmp = ctr.readUint8(csr++); instManLocked->setEnvelopeFMParameter(idx, params.at(FMOperatorParameter::KS), tmp >> 5); instManLocked->setEnvelopeFMParameter(idx, params.at(FMOperatorParameter::DR), tmp & 0x1f); tmp = ctr.readUint8(csr++); instManLocked->setEnvelopeFMParameter(idx, params.at(FMOperatorParameter::DT), tmp >> 5); instManLocked->setEnvelopeFMParameter(idx, params.at(FMOperatorParameter::SR), tmp & 0x1f); tmp = ctr.readUint8(csr++); instManLocked->setEnvelopeFMParameter(idx, params.at(FMOperatorParameter::SL), tmp >> 4); instManLocked->setEnvelopeFMParameter(idx, params.at(FMOperatorParameter::RR), tmp & 0x0f); tmp = ctr.readUint8(csr++); instManLocked->setEnvelopeFMParameter(idx, params.at(FMOperatorParameter::TL), tmp); tmp = ctr.readUint8(csr++); instManLocked->setEnvelopeFMParameter(idx, params.at(FMOperatorParameter::ML), tmp & 0x0f); instManLocked->setEnvelopeFMParameter(idx, params.at(FMOperatorParameter::SSGEG), (tmp & 0x80) ? -1 : ((tmp >> 4) & 0x07)); } instPropCsr += ofs; } break; } case 0x01: // FM LFO { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint8_t ofs = ctr.readUint8(instPropCsr); size_t csr = instPropCsr + 1; uint8_t tmp = ctr.readUint8(csr++); instManLocked->setLFOFMParameter(idx, FMLFOParameter::FREQ, tmp >> 4); instManLocked->setLFOFMParameter(idx, FMLFOParameter::PMS, tmp & 0x0f); tmp = ctr.readUint8(csr++); instManLocked->setLFOFMParameter(idx, FMLFOParameter::AMS, tmp & 0x0f); instManLocked->setLFOFMParameter(idx, FMLFOParameter::AM1, (tmp & 0x10) ? true : false); instManLocked->setLFOFMParameter(idx, FMLFOParameter::AM2, (tmp & 0x20) ? true : false); instManLocked->setLFOFMParameter(idx, FMLFOParameter::AM3, (tmp & 0x40) ? true : false); instManLocked->setLFOFMParameter(idx, FMLFOParameter::AM4, (tmp & 0x80) ? true : false); tmp = ctr.readUint8(csr++); instManLocked->setLFOFMParameter(idx, FMLFOParameter::Count, tmp); instPropCsr += ofs; } break; } case 0x02: // FM AL { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::AL, instPropCsr, instManLocked, ctr, version); break; } case 0x03: // FM FB { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::FB, instPropCsr, instManLocked, ctr, version); break; } case 0x04: // FM AR1 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::AR1, instPropCsr, instManLocked, ctr, version); break; } case 0x05: // FM DR1 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::DR1, instPropCsr, instManLocked, ctr, version); break; } case 0x06: // FM SR1 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::SR1, instPropCsr, instManLocked, ctr, version); break; } case 0x07: // FM RR1 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::RR1, instPropCsr, instManLocked, ctr, version); break; } case 0x08: // FM SL1 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::SL1, instPropCsr, instManLocked, ctr, version); break; } case 0x09: // FM TL1 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::TL1, instPropCsr, instManLocked, ctr, version); break; } case 0x0a: // FM KS1 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::KS1, instPropCsr, instManLocked, ctr, version); break; } case 0x0b: // FM ML1 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::ML1, instPropCsr, instManLocked, ctr, version); break; } case 0x0c: // FM DT1 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::DT1, instPropCsr, instManLocked, ctr, version); break; } case 0x0d: // FM AR2 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::AR2, instPropCsr, instManLocked, ctr, version); break; } case 0x0e: // FM DR2 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::DR2, instPropCsr, instManLocked, ctr, version); break; } case 0x0f: // FM SR2 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::SR2, instPropCsr, instManLocked, ctr, version); break; } case 0x10: // FM RR2 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::RR2, instPropCsr, instManLocked, ctr, version); break; } case 0x11: // FM SL2 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::SL2, instPropCsr, instManLocked, ctr, version); break; } case 0x12: // FM TL2 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::TL2, instPropCsr, instManLocked, ctr, version); break; } case 0x13: // FM KS2 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::KS2, instPropCsr, instManLocked, ctr, version); break; } case 0x14: // FM ML2 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::ML2, instPropCsr, instManLocked, ctr, version); break; } case 0x15: // FM DT2 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::DT2, instPropCsr, instManLocked, ctr, version); break; } case 0x16: // FM AR3 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::AR3, instPropCsr, instManLocked, ctr, version); break; } case 0x17: // FM DR3 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::DR3, instPropCsr, instManLocked, ctr, version); break; } case 0x18: // FM SR3 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::SR3, instPropCsr, instManLocked, ctr, version); break; } case 0x19: // FM RR3 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::RR3, instPropCsr, instManLocked, ctr, version); break; } case 0x1a: // FM SL3 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::SL3, instPropCsr, instManLocked, ctr, version); break; } case 0x1b: // FM TL3 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::TL3, instPropCsr, instManLocked, ctr, version); break; } case 0x1c: // FM KS3 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::KS3, instPropCsr, instManLocked, ctr, version); break; } case 0x1d: // FM ML3 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::ML3, instPropCsr, instManLocked, ctr, version); break; } case 0x1e: // FM DT3 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::DT3, instPropCsr, instManLocked, ctr, version); break; } case 0x1f: // FM AR4 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::AR4, instPropCsr, instManLocked, ctr, version); break; } case 0x20: // FM DR4 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::DR4, instPropCsr, instManLocked, ctr, version); break; } case 0x21: // FM SR4 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::SR4, instPropCsr, instManLocked, ctr, version); break; } case 0x22: // FM RR4 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::RR4, instPropCsr, instManLocked, ctr, version); break; } case 0x23: // FM SL4 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::SL4, instPropCsr, instManLocked, ctr, version); break; } case 0x24: // FM TL4 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::TL4, instPropCsr, instManLocked, ctr, version); break; } case 0x25: // FM KS4 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::KS4, instPropCsr, instManLocked, ctr, version); break; } case 0x26: // FM ML4 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::ML4, instPropCsr, instManLocked, ctr, version); break; } case 0x27: // FM DT4 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadOperatorSequence( FMEnvelopeParameter::DT4, instPropCsr, instManLocked, ctr, version); break; } case 0x28: // FM arpeggio { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (version < Version::toBCD(1, 2, 0)) csr += 2; if (l == 0) instManLocked->setArpeggioFMSequenceData(idx, 0, data); else instManLocked->addArpeggioFMSequenceData(idx, data); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addArpeggioFMLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setArpeggioFMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setArpeggioFMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setArpeggioFMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Mod, csr); } if (version >= Version::toBCD(1, 0, 1)) { switch (ctr.readUint8(csr++)) { case 0x00: // Absolute instManLocked->setArpeggioFMType(idx, SequenceType::AbsoluteSequence); break; case 0x01: // Fixed instManLocked->setArpeggioFMType(idx, SequenceType::FixedSequence); if (version < Version::toBCD(1, 6, 1)) { // Add infinity loop to the last data, to keep compatibility auto loopRt = instManLocked->getArpeggioFMLoopRoot(idx).getAllLoops(); if (std::none_of(loopRt.begin(), loopRt.end(), [](InstrumentSequenceLoop& loop) { return loop.isInfinite(); })) { auto seq = instManLocked->getArpeggioFMSequence(idx); if (!seq.empty()) { size_t pos = seq.size(); instManLocked->addArpeggioFMSequenceData(idx, seq.back().data); instManLocked->addArpeggioFMLoop(idx, InstrumentSequenceLoop(pos, pos, InstrumentSequenceLoop::INFINITE_LOOP)); } } } break; case 0x02: // Relative instManLocked->setArpeggioFMType(idx, SequenceType::RelativeSequence); break; default: if (version < Version::toBCD(1, 3, 2)) { // Recover deep clone bug // https://github.com/BambooTracker/BambooTracker/issues/170 instManLocked->setArpeggioFMType(idx, SequenceType::AbsoluteSequence); break; } else { throw FileCorruptionError(FileType::Mod, csr); } } } instPropCsr += ofs; } break; } case 0x29: // FM pitch { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (version < Version::toBCD(1, 2, 0)) csr += 2; if (l == 0) instManLocked->setPitchFMSequenceData(idx, 0, data); else instManLocked->addPitchFMSequenceData(idx, data); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addPitchFMLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setPitchFMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setPitchFMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setPitchFMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Mod, csr); } if (version >= Version::toBCD(1, 0, 1)) { switch (ctr.readUint8(csr++)) { case 0x00: // Absolute instManLocked->setPitchFMType(idx, SequenceType::AbsoluteSequence); break; case 0x02: // Relative instManLocked->setPitchFMType(idx, SequenceType::RelativeSequence); break; default: if (version < Version::toBCD(1, 3, 2)) { // Recover deep clone bug // https://github.com/BambooTracker/BambooTracker/issues/170 instManLocked->setPitchFMType(idx, SequenceType::AbsoluteSequence); break; } else { throw FileCorruptionError(FileType::Mod, csr); } } } instPropCsr += ofs; } break; } case 0x2a: // FM pan { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (l == 0) instManLocked->setPanFMSequenceData(idx, 0, data); else instManLocked->addPanFMSequenceData(idx, data); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addPanFMLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setPanFMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setPanFMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setPanFMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Mod, csr); } instPropCsr += ofs; } break; } case 0x30: // SSG waveform { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (version < Version::toBCD(1, 2, 0)) { if (data == 3) data = SSGWaveformType::SQM_TRIANGLE; else if (data == 4) data = SSGWaveformType::SQM_SAW; } int32_t subdata; if (version >= Version::toBCD(1, 2, 0)) { subdata = ctr.readInt32(csr); csr += 4; } else { subdata = ctr.readUint16(csr); csr += 2; if (subdata != -1) subdata = note_utils::calculateSSGSquareTP(subdata, 0); } SSGWaveformUnit unit; switch (data) { case SSGWaveformType::SQM_TRIANGLE: case SSGWaveformType::SQM_SAW: case SSGWaveformType::SQM_INVSAW: unit = SSGWaveformUnit::makeUnitWithDecode(data, subdata); break; default: unit = SSGWaveformUnit::makeOnlyDataUnit(data); break; } if (l == 0) instManLocked->setWaveformSSGSequenceData(idx, 0, unit); else instManLocked->addWaveformSSGSequenceData(idx, unit); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addWaveformSSGLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setWaveformSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setWaveformSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setWaveformSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Mod, csr); } if (version >= Version::toBCD(1, 0, 1)) { ++csr; // Skip sequence type } instPropCsr += ofs; } break; } case 0x31: // SSG tone/noise { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (version < Version::toBCD(1, 3, 1)) { if (data > 0) { uint16_t tmp = data - 1; data = tmp / 32 * 32 + (31 - tmp % 32) + 1; } } if (version < Version::toBCD(1, 2, 0)) csr += 2; if (l == 0) instManLocked->setToneNoiseSSGSequenceData(idx, 0, data); else instManLocked->addToneNoiseSSGSequenceData(idx, data); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addToneNoiseSSGLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setToneNoiseSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setToneNoiseSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setToneNoiseSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Mod, csr); } if (version >= Version::toBCD(1, 0, 1)) { ++csr; // Skip sequence type } instPropCsr += ofs; } break; } case 0x32: // SSG envelope { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; int32_t subdata; if (version >= Version::toBCD(1, 2, 0)) { subdata = ctr.readInt32(csr); csr += 4; } else { subdata = ctr.readUint16(csr); csr += 2; } SSGEnvelopeUnit unit = (data < 16) ? SSGEnvelopeUnit::makeOnlyDataUnit(data) : SSGEnvelopeUnit::makeUnitWithDecode(data, subdata); if (l == 0) instManLocked->setEnvelopeSSGSequenceData(idx, 0, unit); else instManLocked->addEnvelopeSSGSequenceData(idx, unit); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addEnvelopeSSGLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setEnvelopeSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; if (pos < seqLen) instManLocked->setEnvelopeSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setEnvelopeSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } case 0x02: // Absolute { uint16_t pos = ctr.readUint16(csr); csr += 2; if (pos < seqLen) instManLocked->setEnvelopeSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::AbsoluteRelease, pos)); else instManLocked->setEnvelopeSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } case 0x03: // Relative { uint16_t pos = ctr.readUint16(csr); csr += 2; if (pos < seqLen) instManLocked->setEnvelopeSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::RelativeRelease, pos)); else instManLocked->setEnvelopeSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Mod, csr); } if (version >= Version::toBCD(1, 0, 1)) { ++csr; // Skip sequence type } instPropCsr += ofs; } break; } case 0x33: // SSG arpeggio { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (version < Version::toBCD(1, 2, 0)) csr += 2; if (l == 0) instManLocked->setArpeggioSSGSequenceData(idx, 0, data); else instManLocked->addArpeggioSSGSequenceData(idx, data); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addArpeggioSSGLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setArpeggioSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setArpeggioSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setArpeggioSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Mod, csr); } if (version >= Version::toBCD(1, 0, 1)) { switch (ctr.readUint8(csr++)) { case 0x00: // Absolute instManLocked->setArpeggioSSGType(idx, SequenceType::AbsoluteSequence); break; case 0x01: // Fixed instManLocked->setArpeggioSSGType(idx, SequenceType::FixedSequence); if (version < Version::toBCD(1, 6, 1)) { // Add infinity loop to the last data, to keep compatibility auto loopRt = instManLocked->getArpeggioSSGLoopRoot(idx).getAllLoops(); if (std::none_of(loopRt.begin(), loopRt.end(), [](InstrumentSequenceLoop& loop) { return loop.isInfinite(); })) { auto seq = instManLocked->getArpeggioSSGSequence(idx); if (!seq.empty()) { size_t pos = seq.size(); instManLocked->addArpeggioSSGSequenceData(idx, seq.back().data); instManLocked->addArpeggioSSGLoop(idx, InstrumentSequenceLoop(pos, pos, InstrumentSequenceLoop::INFINITE_LOOP)); } } } break; case 0x02: // Relative instManLocked->setArpeggioSSGType(idx, SequenceType::RelativeSequence); break; default: if (version < Version::toBCD(1, 3, 2)) { // Recover deep clone bug // https://github.com/BambooTracker/BambooTracker/issues/170 instManLocked->setArpeggioSSGType(idx, SequenceType::AbsoluteSequence); break; } else { throw FileCorruptionError(FileType::Mod, csr); } } } instPropCsr += ofs; } break; } case 0x34: // SSG pitch { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (version < Version::toBCD(1, 2, 0)) csr += 2; if (l == 0) instManLocked->setPitchSSGSequenceData(idx, 0, data); else instManLocked->addPitchSSGSequenceData(idx, data); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addPitchSSGLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setPitchSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setPitchSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setPitchSSGRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Mod, csr); } if (version >= Version::toBCD(1, 0, 1)) { switch (ctr.readUint8(csr++)) { case 0x00: // Absolute instManLocked->setPitchSSGType(idx, SequenceType::AbsoluteSequence); break; case 0x02: // Relative instManLocked->setPitchSSGType(idx, SequenceType::RelativeSequence); break; default: if (version < Version::toBCD(1, 3, 2)) { // Recover deep clone bug // https://github.com/BambooTracker/BambooTracker/issues/170 instManLocked->setPitchSSGType(idx, SequenceType::AbsoluteSequence); break; } else { throw FileCorruptionError(FileType::Mod, csr); } } } instPropCsr += ofs; } break; } case 0x40: // ADPCM sample { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint32_t ofs = ctr.readUint32(instPropCsr); size_t csr = instPropCsr + 4; instManLocked->setSampleADPCMRootKeyNumber(idx, ctr.readUint8(csr++)); instManLocked->setSampleADPCMRootDeltaN(idx, ctr.readUint16(csr)); csr += 2; instManLocked->setSampleADPCMRepeatEnabled(idx, (ctr.readUint8(csr++) & 0x01) != 0); uint32_t len = ctr.readUint32(csr); csr += 4; std::vector samples = ctr.getSubcontainer(csr, len).toVector(); csr += len; instManLocked->storeSampleADPCMRawSample(idx, samples); if (version >= Version::toBCD(1, 6, 1)) { uint16_t repeatBegin = ctr.readUint16(csr); csr += 2; uint16_t repeatEnd = ctr.readUint16(csr); csr += 2; instManLocked->setSampleADPCMRepeatrange(idx, SampleRepeatRange(repeatBegin, repeatEnd)); } else { instManLocked->setSampleADPCMRepeatrange(idx, SampleRepeatRange(0, (samples.size() - 1) >> 5)); } instPropCsr += ofs; } break; } case 0x41: // ADPCM envelope { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (l == 0) instManLocked->setEnvelopeADPCMSequenceData(idx, 0, data); else instManLocked->addEnvelopeADPCMSequenceData(idx, data); if (version < Version::toBCD(1, 6, 0)) csr += 4; } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addEnvelopeADPCMLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setEnvelopeADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; if (pos < seqLen) instManLocked->setEnvelopeADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setEnvelopeADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } case 0x02: // Absolute { uint16_t pos = ctr.readUint16(csr); csr += 2; if (pos < seqLen) instManLocked->setEnvelopeADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::AbsoluteRelease, pos)); else instManLocked->setEnvelopeADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } case 0x03: // Relative { uint16_t pos = ctr.readUint16(csr); csr += 2; if (pos < seqLen) instManLocked->setEnvelopeADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::RelativeRelease, pos)); else instManLocked->setEnvelopeADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Mod, csr); } ++csr; // Skip sequence type instPropCsr += ofs; } break; } case 0x42: // ADPCM arpeggio { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (l == 0) instManLocked->setArpeggioADPCMSequenceData(idx, 0, data); else instManLocked->addArpeggioADPCMSequenceData(idx, data); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addArpeggioADPCMLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setArpeggioADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setArpeggioADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setArpeggioADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Mod, csr); } switch (ctr.readUint8(csr++)) { case 0x00: // Absolute instManLocked->setArpeggioADPCMType(idx, SequenceType::AbsoluteSequence); break; case 0x01: // Fixed instManLocked->setArpeggioADPCMType(idx, SequenceType::FixedSequence); if (version < Version::toBCD(1, 6, 1)) { // Add infinity loop to the last data, to keep compatibility auto loopRt = instManLocked->getArpeggioADPCMLoopRoot(idx).getAllLoops(); if (std::none_of(loopRt.begin(), loopRt.end(), [](InstrumentSequenceLoop& loop) { return loop.isInfinite(); })) { auto seq = instManLocked->getArpeggioADPCMSequence(idx); if (!seq.empty()) { size_t pos = seq.size(); instManLocked->addArpeggioADPCMSequenceData(idx, seq.back().data); instManLocked->addArpeggioADPCMLoop(idx, InstrumentSequenceLoop(pos, pos, InstrumentSequenceLoop::INFINITE_LOOP)); } } } break; case 0x02: // Relative instManLocked->setArpeggioADPCMType(idx, SequenceType::RelativeSequence); break; default: throw FileCorruptionError(FileType::Mod, csr); } instPropCsr += ofs; } break; } case 0x43: // ADPCM pitch { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (l == 0) instManLocked->setPitchADPCMSequenceData(idx, 0, data); else instManLocked->addPitchADPCMSequenceData(idx, data); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addPitchADPCMLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setPitchADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setPitchADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setPitchADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Mod, csr); } switch (ctr.readUint8(csr++)) { case 0x00: // Absolute instManLocked->setPitchADPCMType(idx, SequenceType::AbsoluteSequence); break; case 0x02: // Relative instManLocked->setPitchADPCMType(idx, SequenceType::RelativeSequence); break; default: throw FileCorruptionError(FileType::Mod, csr); } instPropCsr += ofs; } break; } case 0x44: // ADPCM pan { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (l == 0) instManLocked->setPanADPCMSequenceData(idx, 0, data); else instManLocked->addPanADPCMSequenceData(idx, data); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < loopCnt; ++l) { int begin = ctr.readUint16(csr); csr += 2; int end = ctr.readUint16(csr); csr += 2; int times = ctr.readUint8(csr++); instManLocked->addPanADPCMLoop(idx, InstrumentSequenceLoop(begin, end, times)); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instManLocked->setPanADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/BambooTracker/BambooTracker/issues/11 if (pos < seqLen) instManLocked->setPanADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::FixedRelease, pos)); else instManLocked->setPanADPCMRelease(idx, InstrumentSequenceRelease(InstrumentSequenceRelease::NoRelease)); break; } default: throw FileCorruptionError(FileType::Mod, csr); } instPropCsr += ofs; } break; } default: throw FileCorruptionError(FileType::Mod, instPropCsr); } } return globCsr; } size_t loadGrooveSection(std::weak_ptr mod, const BinaryContainer& ctr, size_t globCsr, uint32_t version) { (void)version; std::shared_ptr modLocked = mod.lock(); size_t grvOfs = ctr.readUint32(globCsr); size_t grvCsr = globCsr + 4; uint8_t cnt = ctr.readUint8(grvCsr++) + 1; for (uint8_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(grvCsr++); uint8_t seqLen = ctr.readUint8(grvCsr++); std::vector seq; for (uint8_t l = 0; l < seqLen; ++l) { seq.push_back(ctr.readUint8(grvCsr++)); } if (idx > 0) modLocked->addGroove(); modLocked->setGroove(idx, seq); } return globCsr + grvOfs; } size_t loadSongSection(std::weak_ptr mod, const BinaryContainer& ctr, size_t globCsr, uint32_t version) { std::shared_ptr modLocked = mod.lock(); size_t songOfs = ctr.readUint32(globCsr); size_t songCsr = globCsr + 4; uint8_t cnt = ctr.readUint8(songCsr++); for (uint8_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(songCsr++); size_t sOfs = ctr.readUint32(songCsr); size_t scsr = songCsr + 4; songCsr += sOfs; size_t titleLen = ctr.readUint32(scsr); scsr += 4; std::string title = u8""; if (titleLen > 0) title = ctr.readString(scsr, titleLen); scsr += titleLen; uint32_t tempo = ctr.readUint32(scsr); scsr += 4; uint8_t groove = ctr.readUint8(scsr); scsr += 1; bool isTempo = (groove & 0x80) ? true : false; groove &= 0x7f; uint32_t speed = ctr.readUint32(scsr); scsr += 4; size_t ptnSize = ctr.readUint8(scsr) + 1; scsr += 1; SongType songType; switch (ctr.readUint8(scsr++)) { case 0x00: songType = SongType::Standard; break; case 0x01: songType = SongType::FM3chExpanded; break; default: throw FileCorruptionError(FileType::Mod, scsr); } modLocked->addSong(idx, songType, title, isTempo, static_cast(tempo), groove, static_cast(speed), ptnSize); auto& song = modLocked->getSong(idx); if (version >= Version::toBCD(1, 6, 0)) { const uint8_t invisCnt = ctr.readUint8(scsr++); for (uint8_t invis = 0; invis < invisCnt; ++invis) { song.getTrack(ctr.readUint8(scsr++)).setVisibility(false); } } // Bookmark if (Version::toBCD(1, 4, 1) <= version) { int bmSize = ctr.readUint8(scsr++); for (int i = 0; i < bmSize; ++i) { size_t len = ctr.readUint32(scsr); scsr += 4; std::string name = ctr.readString(scsr, len); scsr += len; int order = ctr.readUint8(scsr++); int step = ctr.readUint8(scsr++); song.addBookmark(name, order, step); } } // Bookmark if (Version::toBCD(1, 6, 0) <= version) { int sgnSize = ctr.readUint8(scsr++); for (int i = 0; i < sgnSize; ++i) { auto type = static_cast(ctr.readUint8(scsr++)); int order = ctr.readUint8(scsr++); int step = ctr.readUint8(scsr++); song.addKeySignature(type, order, step); } } while (scsr < songCsr) { // Track uint8_t trackIdx = ctr.readUint8(scsr++); auto& track = song.getTrack(trackIdx); size_t trackOfs = ctr.readUint32(scsr); size_t trackEnd = scsr + trackOfs; size_t tcsr = scsr + 4; uint8_t odrLen = ctr.readUint8(tcsr++) + 1; for (uint8_t oi = 0; oi < odrLen; ++oi) { if (!oi) track.registerPatternToOrder(oi, ctr.readUint8(tcsr++)); else { track.insertOrderBelow(oi - 1); track.registerPatternToOrder(oi, ctr.readUint8(tcsr++)); } } if (version >= Version::toBCD(1, 2, 1)) { track.setEffectDisplayWidth(ctr.readUint8(tcsr++)); } SoundSource sndsrc = track.getAttribute().source; // Pattern while (tcsr < trackEnd) { uint8_t ptnIdx = ctr.readUint8(tcsr++); auto& pattern = track.getPattern(ptnIdx); size_t ptnOfs = ctr.readUint32(tcsr); size_t pcsr = tcsr + 4; tcsr += ptnOfs; // Step while (pcsr < tcsr) { uint32_t stepIdx = ctr.readUint8(pcsr++); auto& step = pattern.getStep(static_cast(stepIdx)); uint16_t eventFlag = ctr.readUint16(pcsr); pcsr += 2; if (eventFlag & 0x0001) { if (version >= Version::toBCD(1, 0, 2)) { step.setNoteNumber(ctr.readInt8(pcsr++)); } else { // Change FM octave (song type is only 0x00 before v1.0.2) int8_t nn = ctr.readInt8(pcsr++); if (trackIdx < 6 && 0 <= nn && nn < 84) step.setNoteNumber(nn + 12); else step.setNoteNumber(nn); } } if (eventFlag & 0x0002) step.setInstrumentNumber(ctr.readUint8(pcsr++)); if (eventFlag & 0x0004) step.setVolume(ctr.readUint8(pcsr++)); EffectType efftype = EffectType::NoEffect; if (eventFlag & 0x0008) { std::string id = ctr.readString(pcsr, 2); step.setEffectId(0, id); efftype = effect_utils::validateEffectId(sndsrc, id); pcsr += 2; } if (eventFlag & 0x0010) { int v = ctr.readUint8(pcsr++); if (version < Version::toBCD(1, 3, 1) && efftype == EffectType::NoisePitch && v < 32) v = 31 - v; step.setEffectValue(0, v); } efftype = EffectType::NoEffect; if (eventFlag & 0x0020) { std::string id = ctr.readString(pcsr, 2); step.setEffectId(1, id); efftype = effect_utils::validateEffectId(sndsrc, id); pcsr += 2; } if (eventFlag & 0x0040) { int v = ctr.readUint8(pcsr++); if (version < Version::toBCD(1, 3, 1) && efftype == EffectType::NoisePitch && v < 32) v = 31 - v; step.setEffectValue(1, v); } efftype = EffectType::NoEffect; if (eventFlag & 0x0080) { std::string id = ctr.readString(pcsr, 2); step.setEffectId(2, id); efftype = effect_utils::validateEffectId(sndsrc, id); pcsr += 2; } if (eventFlag & 0x0100) { int v = ctr.readUint8(pcsr++); if (version < Version::toBCD(1, 3, 1) && efftype == EffectType::NoisePitch && v < 32) v = 31 - v; step.setEffectValue(2, v); } efftype = EffectType::NoEffect; if (eventFlag & 0x0200) { std::string id = ctr.readString(pcsr, 2); step.setEffectId(3, id); efftype = effect_utils::validateEffectId(sndsrc, id); pcsr += 2; } if (eventFlag & 0x0400) { int v = ctr.readUint8(pcsr++); if (version < Version::toBCD(1, 3, 1) && efftype == EffectType::NoisePitch && v < 32) v = 31 - v; step.setEffectValue(3, v); } } } scsr += trackOfs; } if (version < Version::toBCD(1, 4, 0)) { // ADPCM track int odrLen = static_cast(song.getOrderSize()); int trackNum; switch (songType) { case SongType::Standard: trackNum = 15; break; case SongType::FM3chExpanded: trackNum = 18; break; } auto& track = song.getTrack(trackNum); for (int oi = 0; oi < odrLen; ++oi) { if (oi) track.insertOrderBelow(oi - 1); track.registerPatternToOrder(oi, 0); } track.setEffectDisplayWidth(0); } } return globCsr + songOfs; } } BtmIO::BtmIO() : AbstractModuleIO("btm", "BambooTracker module", true, true) {} void BtmIO::load(const BinaryContainer& ctr, std::weak_ptr mod, std::weak_ptr instMan) const { size_t globCsr = 0; if (ctr.readString(globCsr, 16) != "BambooTrackerMod") throw FileCorruptionError(FileType::Mod, globCsr); globCsr += 16; size_t eofOfs = ctr.readUint32(globCsr); size_t eof = globCsr + eofOfs; globCsr += 4; size_t fileVersion = ctr.readUint32(globCsr); if (fileVersion > Version::ofModuleFileInBCD()) throw FileVersionError(FileType::Mod); globCsr += 4; while (globCsr < eof) { if (ctr.readString(globCsr, 8) == "MODULE ") globCsr = loadModuleSection(mod, ctr, globCsr + 8, fileVersion); else if (ctr.readString(globCsr, 8) == "INSTRMNT") globCsr = loadInstrumentSection(instMan, ctr, globCsr + 8, fileVersion); else if (ctr.readString(globCsr, 8) == "INSTPROP") globCsr = loadInstrumentPropertySection(instMan, ctr, globCsr + 8, fileVersion); else if (ctr.readString(globCsr, 8) == "GROOVE ") globCsr = loadGrooveSection(mod, ctr, globCsr + 8, fileVersion); else if (ctr.readString(globCsr, 8) == "SONG ") globCsr = loadSongSection(mod, ctr, globCsr + 8, fileVersion); else throw FileCorruptionError(FileType::Mod, globCsr); } } void BtmIO::save(BinaryContainer& ctr, const std::weak_ptr mod, const std::weak_ptr instMan) const { std::shared_ptr instManLocked = instMan.lock(); ctr.appendString("BambooTrackerMod"); size_t eofOfs = ctr.size(); ctr.appendUint32(0); // Dummy EOF offset uint32_t fileVersion = Version::ofModuleFileInBCD(); ctr.appendUint32(fileVersion); /***** Module section *****/ ctr.appendString("MODULE "); size_t modOfs = ctr.size(); ctr.appendUint32(0); // Dummy module section offset std::string modTitle = mod.lock()->getTitle(); ctr.appendUint32(modTitle.length()); if (!modTitle.empty()) ctr.appendString(modTitle); std::string author = mod.lock()->getAuthor(); ctr.appendUint32(author.length()); if (!author.empty()) ctr.appendString(author); std::string copyright = mod.lock()->getCopyright(); ctr.appendUint32(copyright.length()); if (!copyright.empty()) ctr.appendString(copyright); std::string comment = mod.lock()->getComment(); ctr.appendUint32(comment.length()); if (!comment.empty()) ctr.appendString(comment); ctr.appendUint32(mod.lock()->getTickFrequency()); ctr.appendUint32(mod.lock()->getStepHighlight1Distance()); ctr.appendUint32(mod.lock()->getStepHighlight2Distance()); MixerType mixType = mod.lock()->getMixerType(); ctr.appendUint8(static_cast(mixType)); if (mixType == MixerType::CUSTOM) { ctr.appendInt8(static_cast(mod.lock()->getCustomMixerFMLevel() * 10)); ctr.appendInt8(static_cast(mod.lock()->getCustomMixerSSGLevel() * 10)); } ctr.writeUint32(modOfs, ctr.size() - modOfs); /***** Instrument section *****/ ctr.appendString("INSTRMNT"); size_t instOfs = ctr.size(); ctr.appendUint32(0); // Dummy instrument section offset std::vector instIdcs = instManLocked->getInstrumentIndices(); ctr.appendUint8(static_cast(instIdcs.size())); for (auto& idx : instIdcs) { if (std::shared_ptr inst = instManLocked->getInstrumentSharedPtr(idx)) { ctr.appendUint8(static_cast(inst->getNumber())); size_t iOfs = ctr.size(); ctr.appendUint32(0); // Dummy instrument block offset std::string name = inst->getName(); ctr.appendUint32(name.length()); if (!name.empty()) ctr.appendString(name); switch (inst->getType()) { case InstrumentType::FM: { ctr.appendUint8(0x00); auto instFM = std::dynamic_pointer_cast(inst); ctr.appendUint8(static_cast(instFM->getEnvelopeNumber())); uint8_t tmp = static_cast(instFM->getLFONumber()); ctr.appendUint8(instFM->getLFOEnabled() ? tmp : (0x80 | tmp)); for (auto& param : FM_OPSEQ_PARAMS) { tmp = static_cast(instFM->getOperatorSequenceNumber(param)); ctr.appendUint8(instFM->getOperatorSequenceEnabled(param) ? tmp : (0x80 | tmp)); } tmp = static_cast(instFM->getArpeggioNumber(FMOperatorType::All)); ctr.appendUint8(instFM->getArpeggioEnabled(FMOperatorType::All) ? tmp : (0x80 | tmp)); tmp = static_cast(instFM->getPitchNumber(FMOperatorType::All)); ctr.appendUint8(instFM->getPitchEnabled(FMOperatorType::All) ? tmp : (0x80 | tmp)); tmp = static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::All)) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op1) << 1) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op2) << 2) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op3) << 3) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op4) << 4); ctr.appendUint8(tmp); for (auto& type : FM_OP_TYPES) { tmp = static_cast(instFM->getArpeggioNumber(type)); ctr.appendUint8(instFM->getArpeggioEnabled(type) ? tmp : (0x80 | tmp)); } for (auto& type : FM_OP_TYPES) { tmp = static_cast(instFM->getPitchNumber(type)); ctr.appendUint8(instFM->getPitchEnabled(type) ? tmp : (0x80 | tmp)); } tmp = static_cast(instFM->getPanNumber()); ctr.appendUint8(instFM->getPanEnabled() ? tmp : (0x80 | tmp)); break; } case InstrumentType::SSG: { ctr.appendUint8(0x01); auto instSSG = std::dynamic_pointer_cast(inst); uint8_t tmp = static_cast(instSSG->getWaveformNumber()); ctr.appendUint8(instSSG->getWaveformEnabled() ? tmp : (0x80 | tmp)); tmp = static_cast(instSSG->getToneNoiseNumber()); ctr.appendUint8(instSSG->getToneNoiseEnabled() ? tmp : (0x80 | tmp)); tmp = static_cast(instSSG->getEnvelopeNumber()); ctr.appendUint8(instSSG->getEnvelopeEnabled() ? tmp : (0x80 | tmp)); tmp = static_cast(instSSG->getArpeggioNumber()); ctr.appendUint8(instSSG->getArpeggioEnabled() ? tmp : (0x80 | tmp)); tmp = static_cast(instSSG->getPitchNumber()); ctr.appendUint8(instSSG->getPitchEnabled() ? tmp : (0x80 | tmp)); break; } case InstrumentType::ADPCM: { ctr.appendUint8(0x02); auto instADPCM = std::dynamic_pointer_cast(inst); ctr.appendUint8(static_cast(instADPCM->getSampleNumber())); uint8_t tmp = static_cast(instADPCM->getEnvelopeNumber()); ctr.appendUint8(instADPCM->getEnvelopeEnabled() ? tmp : (0x80 | tmp)); tmp = static_cast(instADPCM->getArpeggioNumber()); ctr.appendUint8(instADPCM->getArpeggioEnabled() ? tmp : (0x80 | tmp)); tmp = static_cast(instADPCM->getPitchNumber()); ctr.appendUint8(instADPCM->getPitchEnabled() ? tmp : (0x80 | tmp)); tmp = static_cast(instADPCM->getPanNumber()); ctr.appendUint8(instADPCM->getPanEnabled() ? tmp : (0x80 | tmp)); break; } case InstrumentType::Drumkit: { ctr.appendUint8(0x03); auto instKit = std::dynamic_pointer_cast(inst); std::vector keys = instKit->getAssignedKeys(); ctr.appendUint8(static_cast(keys.size())); for (const int& key : keys) { ctr.appendUint8(static_cast(key)); ctr.appendUint8(static_cast(instKit->getSampleNumber(key))); ctr.appendInt8(static_cast(instKit->getPitch(key))); ctr.appendUint8(static_cast(instKit->getPan(key))); } break; } } ctr.writeUint32(iOfs, ctr.size() - iOfs); } } ctr.writeUint32(instOfs, ctr.size() - instOfs); /***** Instrument property section *****/ ctr.appendString("INSTPROP"); size_t instPropOfs = ctr.size(); ctr.appendUint32(0); // Dummy instrument property section offset // FM envelope std::vector envFMIdcs = instManLocked->getEnvelopeFMEntriedIndices(); if (!envFMIdcs.empty()) { ctr.appendUint8(0x00); ctr.appendUint8(static_cast(envFMIdcs.size())); for (auto& idx : envFMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint8(0); // Dummy offset uint8_t tmp = static_cast(instManLocked->getEnvelopeFMParameter(idx, FMEnvelopeParameter::AL) << 4) | static_cast(instManLocked->getEnvelopeFMParameter(idx, FMEnvelopeParameter::FB)); ctr.appendUint8(tmp); for (int op = 0; op < 4; ++op) { auto& params = FM_OP_PARAMS[op]; tmp = instManLocked->getEnvelopeFMOperatorEnabled(idx, op); tmp = static_cast((tmp << 5)) | static_cast(instManLocked->getEnvelopeFMParameter(idx, params.at(FMOperatorParameter::AR))); ctr.appendUint8(tmp); tmp = static_cast(instManLocked->getEnvelopeFMParameter(idx, params.at(FMOperatorParameter::KS)) << 5) | static_cast(instManLocked->getEnvelopeFMParameter(idx, params.at(FMOperatorParameter::DR))); ctr.appendUint8(tmp); tmp = static_cast(instManLocked->getEnvelopeFMParameter(idx, params.at(FMOperatorParameter::DT)) << 5) | static_cast(instManLocked->getEnvelopeFMParameter(idx, params.at(FMOperatorParameter::SR))); ctr.appendUint8(tmp); tmp = static_cast(instManLocked->getEnvelopeFMParameter(idx, params.at(FMOperatorParameter::SL)) << 4) | static_cast(instManLocked->getEnvelopeFMParameter(idx, params.at(FMOperatorParameter::RR))); ctr.appendUint8(tmp); tmp = static_cast(instManLocked->getEnvelopeFMParameter(idx, params.at(FMOperatorParameter::TL))); ctr.appendUint8(tmp); int tmp2 = instManLocked->getEnvelopeFMParameter(idx, params.at(FMOperatorParameter::SSGEG)); tmp = ((tmp2 == -1) ? 0x80 : static_cast(tmp2 << 4)) | static_cast(instManLocked->getEnvelopeFMParameter(idx, params.at(FMOperatorParameter::ML))); ctr.appendUint8(tmp); } ctr.writeUint8(ofs, static_cast(ctr.size() - ofs)); } } // FM LFO std::vector lfoFMIdcs = instManLocked->getLFOFMEntriedIndices(); if (!lfoFMIdcs.empty()) { ctr.appendUint8(0x01); ctr.appendUint8(static_cast(lfoFMIdcs.size())); for (auto& idx : lfoFMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint8(0); // Dummy offset uint8_t tmp = static_cast(instManLocked->getLFOFMparameter(idx, FMLFOParameter::FREQ) << 4) | static_cast(instManLocked->getLFOFMparameter(idx, FMLFOParameter::PMS)); ctr.appendUint8(tmp); tmp = static_cast(instManLocked->getLFOFMparameter(idx, FMLFOParameter::AM4) << 7) | static_cast(instManLocked->getLFOFMparameter(idx, FMLFOParameter::AM3) << 6) | static_cast(instManLocked->getLFOFMparameter(idx, FMLFOParameter::AM2) << 5) | static_cast(instManLocked->getLFOFMparameter(idx, FMLFOParameter::AM1) << 4) | static_cast(instManLocked->getLFOFMparameter(idx, FMLFOParameter::AMS)); ctr.appendUint8(tmp); tmp = static_cast(instManLocked->getLFOFMparameter(idx, FMLFOParameter::Count)); ctr.appendUint8(tmp); ctr.writeUint8(ofs, static_cast(ctr.size() - ofs)); } } // FM envelope parameter for (size_t i = 0; i < 38; ++i) { std::vector idcs = instManLocked->getOperatorSequenceFMEntriedIndices(FM_OPSEQ_PARAMS[i]); if (!idcs.empty()) { ctr.appendUint8(0x02 + static_cast(i)); ctr.appendUint8(static_cast(idcs.size())); for (auto& idx : idcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getOperatorSequenceFMSequence(FM_OPSEQ_PARAMS[i], idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instManLocked->getOperatorSequenceFMLoopRoot(FM_OPSEQ_PARAMS[i], idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getOperatorSequenceFMRelease(FM_OPSEQ_PARAMS[i], idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } } // FM arpeggio std::vector arpFMIdcs = instManLocked->getArpeggioFMEntriedIndices(); if (!arpFMIdcs.empty()) { ctr.appendUint8(0x28); ctr.appendUint8(static_cast(arpFMIdcs.size())); for (auto& idx : arpFMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getArpeggioFMSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instManLocked->getArpeggioFMLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getArpeggioFMRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } switch (instManLocked->getArpeggioFMType(idx)) { case SequenceType::AbsoluteSequence: ctr.appendUint8(0x00); break; case SequenceType::FixedSequence: ctr.appendUint8(0x01); break; case SequenceType::RelativeSequence: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // FM pitch std::vector ptFMIdcs = instManLocked->getPitchFMEntriedIndices(); if (!ptFMIdcs.empty()) { ctr.appendUint8(0x29); ctr.appendUint8(static_cast(ptFMIdcs.size())); for (auto& idx : ptFMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getPitchFMSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instManLocked->getPitchFMLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getPitchFMRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } switch (instManLocked->getPitchFMType(idx)) { case SequenceType::AbsoluteSequence: ctr.appendUint8(0x00); break; case SequenceType::RelativeSequence: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // FM pan std::vector panFMIdcs = instManLocked->getPanFMEntriedIndices(); if (!panFMIdcs.empty()) { ctr.appendUint8(0x2a); ctr.appendUint8(static_cast(panFMIdcs.size())); for (auto& idx : panFMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getPanFMSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instManLocked->getPanFMLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getPanFMRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // SSG waveform std::vector wfSSGIdcs = instManLocked->getWaveformSSGEntriedIndices(); if (!wfSSGIdcs.empty()) { ctr.appendUint8(0x30); ctr.appendUint8(static_cast(wfSSGIdcs.size())); for (auto& idx : wfSSGIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getWaveformSSGSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); ctr.appendInt32(static_cast(unit.subdata)); } auto loops = instManLocked->getWaveformSSGLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getWaveformSSGRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // SSG tone/noise std::vector tnSSGIdcs = instManLocked->getToneNoiseSSGEntriedIndices(); if (!tnSSGIdcs.empty()) { ctr.appendUint8(0x31); ctr.appendUint8(static_cast(tnSSGIdcs.size())); for (auto& idx : tnSSGIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getToneNoiseSSGSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instManLocked->getToneNoiseSSGLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getToneNoiseSSGRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // SSG envelope std::vector envSSGIdcs = instManLocked->getEnvelopeSSGEntriedIndices(); if (!envSSGIdcs.empty()) { ctr.appendUint8(0x32); ctr.appendUint8(static_cast(envSSGIdcs.size())); for (auto& idx : envSSGIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getEnvelopeSSGSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); ctr.appendInt32(static_cast(unit.subdata)); } auto loops = instManLocked->getEnvelopeSSGLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getEnvelopeSSGRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // SSG arpeggio std::vector arpSSGIdcs = instManLocked->getArpeggioSSGEntriedIndices(); if (!arpSSGIdcs.empty()) { ctr.appendUint8(0x33); ctr.appendUint8(static_cast(arpSSGIdcs.size())); for (auto& idx : arpSSGIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getArpeggioSSGSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instManLocked->getArpeggioSSGLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getArpeggioSSGRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } switch (instManLocked->getArpeggioSSGType(idx)) { case SequenceType::AbsoluteSequence: ctr.appendUint8(0x00); break; case SequenceType::FixedSequence: ctr.appendUint8(0x01); break; case SequenceType::RelativeSequence: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // SSG pitch std::vector ptSSGIdcs = instManLocked->getPitchSSGEntriedIndices(); if (!ptSSGIdcs.empty()) { ctr.appendUint8(0x34); ctr.appendUint8(static_cast(ptSSGIdcs.size())); for (auto& idx : ptSSGIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getPitchSSGSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instManLocked->getPitchSSGLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getPitchSSGRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } switch (instManLocked->getPitchSSGType(idx)) { case SequenceType::AbsoluteSequence: ctr.appendUint8(0x00); break; case SequenceType::RelativeSequence: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // ADPCM sample std::vector sampADPCMIdcs = instManLocked->getSampleADPCMEntriedIndices(); if (!sampADPCMIdcs.empty()) { ctr.appendUint8(0x40); ctr.appendUint8(static_cast(sampADPCMIdcs.size())); for (auto& idx : sampADPCMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint32(0); // Dummy offset ctr.appendUint8(static_cast(instManLocked->getSampleADPCMRootKeyNumber(idx))); ctr.appendUint16(static_cast(instManLocked->getSampleADPCMRootDeltaN(idx))); ctr.appendUint8(static_cast(instManLocked->isSampleADPCMRepeatable(idx))); std::vector samples = instManLocked->getSampleADPCMRawSample(idx); ctr.appendUint32(samples.size()); ctr.appendVector(std::move(samples)); SampleRepeatRange range = instMan.lock()->getSampleADPCMRepeatRange(idx); ctr.appendUint16(range.first()); ctr.appendUint16(range.last()); ctr.writeUint32(ofs, ctr.size() - ofs); } } // ADPCM envelope std::vector envADPCMIdcs = instManLocked->getEnvelopeADPCMEntriedIndices(); if (!envADPCMIdcs.empty()) { ctr.appendUint8(0x41); ctr.appendUint8(static_cast(envADPCMIdcs.size())); for (auto& idx : envADPCMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getEnvelopeADPCMSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instManLocked->getEnvelopeADPCMLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getEnvelopeADPCMRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // ADPCM arpeggio std::vector arpADPCMIdcs = instManLocked->getArpeggioADPCMEntriedIndices(); if (!arpADPCMIdcs.empty()) { ctr.appendUint8(0x42); ctr.appendUint8(static_cast(arpADPCMIdcs.size())); for (auto& idx : arpADPCMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getArpeggioADPCMSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instManLocked->getArpeggioADPCMLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getArpeggioADPCMRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } switch (instManLocked->getArpeggioADPCMType(idx)) { case SequenceType::AbsoluteSequence: ctr.appendUint8(0x00); break; case SequenceType::FixedSequence: ctr.appendUint8(0x01); break; case SequenceType::RelativeSequence: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // ADPCM pitch std::vector ptADPCMIdcs = instManLocked->getPitchADPCMEntriedIndices(); if (!ptADPCMIdcs.empty()) { ctr.appendUint8(0x43); ctr.appendUint8(static_cast(ptADPCMIdcs.size())); for (auto& idx : ptADPCMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getPitchADPCMSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instManLocked->getPitchADPCMLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getPitchADPCMRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } switch (instManLocked->getPitchADPCMType(idx)) { case SequenceType::AbsoluteSequence: ctr.appendUint8(0x00); break; case SequenceType::RelativeSequence: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // ADPCM pan std::vector panADPCMIdcs = instManLocked->getPanADPCMEntriedIndices(); if (!panADPCMIdcs.empty()) { ctr.appendUint8(0x44); ctr.appendUint8(static_cast(panADPCMIdcs.size())); for (auto& idx : panADPCMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instManLocked->getPanADPCMSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& unit : seq) { ctr.appendUint16(static_cast(unit.data)); } auto loops = instManLocked->getPanADPCMLoopRoot(idx).getAllLoops(); ctr.appendUint16(static_cast(loops.size())); for (auto& loop : loops) { ctr.appendUint16(static_cast(loop.getBeginPos())); ctr.appendUint16(static_cast(loop.getEndPos())); ctr.appendUint8(static_cast(loop.getTimes())); } auto release = instManLocked->getPanADPCMRelease(idx); switch (release.getType()) { case InstrumentSequenceRelease::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case InstrumentSequenceRelease::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.getBeginPos())); break; case InstrumentSequenceRelease::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.getBeginPos())); break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } ctr.writeUint32(instPropOfs, ctr.size() - instPropOfs); /***** Groove section *****/ ctr.appendString("GROOVE "); size_t grooveOfs = ctr.size(); ctr.appendUint32(0); // Dummy groove section offset size_t grooveCnt = mod.lock()->getGrooveCount(); ctr.appendUint8(static_cast(grooveCnt - 1)); for (size_t i = 0; i < grooveCnt; ++i) { ctr.appendUint8(static_cast(i)); auto seq = mod.lock()->getGroove(static_cast(i)); ctr.appendUint8(static_cast(seq.size())); for (auto& g : seq) { ctr.appendUint8(static_cast(g)); } } ctr.writeUint32(grooveOfs, ctr.size() - grooveOfs); /***** Song section *****/ ctr.appendString("SONG "); size_t songSecOfs = ctr.size(); ctr.appendUint32(0); // Dummy song section offset size_t songCnt = mod.lock()->getSongCount(); ctr.appendUint8(static_cast(songCnt)); // Song for (size_t i = 0; i < songCnt; ++i) { ctr.appendUint8(static_cast(i)); size_t songOfs = ctr.size(); ctr.appendUint32(0); // Dummy song block offset auto& sng = mod.lock()->getSong(static_cast(i)); std::string title = sng.getTitle(); ctr.appendUint32(title.length()); if (!title.empty()) ctr.appendString(title); ctr.appendUint32(static_cast(sng.getTempo())); uint8_t tmp = static_cast(sng.getGroove()); ctr.appendUint8(sng.isUsedTempo() ? (0x80 | tmp) : tmp); ctr.appendUint32(static_cast(sng.getSpeed())); ctr.appendUint8(static_cast(sng.getDefaultPatternSize()) - 1); auto style = sng.getStyle(); switch (style.type) { case SongType::Standard: ctr.appendUint8(0x00); break; case SongType::FM3chExpanded: ctr.appendUint8(0x01); break; default: throw std::out_of_range(""); } std::vector inviss; for (const auto& attrib : style.trackAttribs) { if (!sng.getTrack(attrib.number).isVisible()) inviss.push_back(attrib.number); } ctr.appendUint8(static_cast(inviss.size())); if (!inviss.empty()) for (int n : inviss) ctr.appendUint8(static_cast(n)); // Bookmark size_t bmSize = sng.getBookmarkSize(); ctr.appendUint8(static_cast(bmSize)); for (size_t i = 0; i < bmSize; ++i) { Bookmark bm = sng.getBookmark(static_cast(i)); ctr.appendUint32(bm.name.length()); ctr.appendString(bm.name); ctr.appendUint8(static_cast(bm.order)); ctr.appendUint8(static_cast(bm.step)); } // Key signature size_t sgnSize = sng.getKeySignatureSize(); ctr.appendUint8(static_cast(sgnSize)); for (size_t i = 0; i < sgnSize; ++i) { KeySignature signature = sng.getKeySignature(static_cast(i)); ctr.appendUint8(static_cast(signature.type)); ctr.appendUint8(static_cast(signature.order)); ctr.appendUint8(static_cast(signature.step)); } // Track for (auto& attrib : style.trackAttribs) { ctr.appendUint8(static_cast(attrib.number)); size_t trackOfs = ctr.size(); ctr.appendUint32(0); // Dummy track subblock offset auto& track = sng.getTrack(attrib.number); // Order size_t odrSize = track.getOrderSize(); ctr.appendUint8(static_cast(odrSize) - 1); for (size_t o = 0; o < odrSize; ++o) ctr.appendUint8(static_cast(track.getOrderInfo(static_cast(o)).patten)); ctr.appendUint8(static_cast(track.getEffectDisplayWidth())); // Pattern for (auto& idx : track.getEditedPatternIndices()) { ctr.appendUint8(static_cast(idx)); size_t ptnOfs = ctr.size(); ctr.appendUint32(0); // Dummy pattern subblock offset auto& pattern = track.getPattern(idx); // Step std::vector stepIdcs = pattern.getEditedStepIndices(); for (auto& sidx : stepIdcs) { ctr.appendUint8(static_cast(sidx)); size_t evFlagOfs = ctr.size(); ctr.appendUint16(0); // Dummy set event flag auto& step = pattern.getStep(sidx); uint16_t eventFlag = 0; int tmp = step.getNoteNumber(); if (!Step::testEmptyNote(tmp)) { eventFlag |= 0x0001; ctr.appendInt8(static_cast(tmp)); } tmp = step.getInstrumentNumber(); if (!Step::testEmptyInstrument(tmp)) { eventFlag |= 0x0002; ctr.appendUint8(static_cast(tmp)); } tmp = step.getVolume(); if (!Step::testEmptyVolume(tmp)) { eventFlag |= 0x0004; ctr.appendUint8(static_cast(tmp)); } for (int i = 0; i < Step::N_EFFECT; ++i) { std::string tmpstr = step.getEffectId(i); if (!Step::testEmptyEffectId(tmpstr)) { eventFlag |= (0x0008 << (i << 1)); ctr.appendString(tmpstr); } tmp = step.getEffectValue(i); if (!Step::testEmptyEffectValue(tmp)) { eventFlag |= (0x0010 << (i << 1)); ctr.appendUint8(static_cast(tmp)); } } ctr.writeUint16(evFlagOfs, eventFlag); } ctr.writeUint32(ptnOfs, ctr.size() - ptnOfs); } ctr.writeUint32(trackOfs, ctr.size() - trackOfs); } ctr.writeUint32(songOfs, ctr.size() - songOfs); } ctr.writeUint32(songSecOfs, ctr.size() - songSecOfs); ctr.writeUint32(eofOfs, ctr.size() - eofOfs); } } BambooTracker-0.6.5/BambooTracker/io/btm_io.hpp000066400000000000000000000027471476276175200213740ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "module_io.hpp" namespace io { class BtmIO final : public AbstractModuleIO { public: BtmIO(); void load(const BinaryContainer& ctr, std::weak_ptr mod, std::weak_ptr instMan) const override; void save(BinaryContainer& ctr, const std::weak_ptr mod, const std::weak_ptr instMan) const override; }; } BambooTracker-0.6.5/BambooTracker/io/dat_io.cpp000066400000000000000000000100531476276175200213420ustar00rootroot00000000000000/* * Copyright (C) 2020-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "dat_io.hpp" #include #include #include "instrument.hpp" #include "file_io_error.hpp" namespace io { DatIO::DatIO() : AbstractBankIO("dat", "MUCOM88 voice", true, false) {} AbstractBank* DatIO::load(const BinaryContainer& ctr) const { size_t csr = 0; // File size check size_t ctrSize = ctr.size(); if (!ctrSize || ctrSize != 0x2000) throw FileCorruptionError(FileType::Bank, 0); std::vector ids; std::vector names; std::vector ctrs; for (int i = 0; i < 256; ++i) { csr++; // Skip first byte (unknown byte) BinaryContainer block = ctr.getSubcontainer(csr, 25); csr += 25; std::string name = ""; for (size_t j = 0; j < 6; ++j) { if (char c = ctr.readChar(csr + j)) name += c; else break; } csr += 6; // Empty if (std::all_of(block.begin(), block.end(), [](const char c) { return c == 0; }) && name.empty()) continue; ids.push_back(i); names.push_back(name); ctrs.push_back(block); } return new Mucom88Bank(ids, names, ctrs); } AbstractInstrument* DatIO::loadInstrument(const BinaryContainer& instCtr, const std::string& name, std::weak_ptr instMan, int instNum) { std::shared_ptr instManLocked = instMan.lock(); int envIdx = instManLocked->findFirstAssignableEnvelopeFM(); if (envIdx < 0) throw FileCorruptionError(FileType::Bank, 0); InstrumentFM* fm = new InstrumentFM(instNum, name, instManLocked.get()); fm->setEnvelopeNumber(envIdx); uint8_t tmp; size_t initCsr[] = { 0, 2, 1, 3}; for (int op = 0; op < 4; ++op) { size_t csr = initCsr[op]; auto& params = FM_OP_PARAMS[op]; tmp = instCtr.readUint8(csr); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::DT), tmp >> 4); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::ML), tmp & 0x0f); csr += 4; tmp = instCtr.readUint8(csr); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::TL), tmp); csr += 4; tmp = instCtr.readUint8(csr); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::KS), tmp >> 6); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::AR), tmp & 0x1f); csr += 4; tmp = instCtr.readUint8(csr); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::DR), tmp & 0x1f); csr += 4; tmp = instCtr.readUint8(csr); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::SR), tmp); csr += 4; tmp = instCtr.readUint8(csr); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::SL), tmp >> 4); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::RR), tmp & 0x0f); } tmp = instCtr.readUint8(24); instManLocked->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::FB, tmp >> 3); instManLocked->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AL, tmp & 0x07); return fm; } } BambooTracker-0.6.5/BambooTracker/io/dat_io.hpp000066400000000000000000000027361476276175200213600ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "bank_io.hpp" namespace io { class DatIO final : public AbstractBankIO { public: DatIO(); AbstractBank* load(const BinaryContainer& ctr) const override; static AbstractInstrument* loadInstrument(const BinaryContainer& instCtr, const std::string& name, std::weak_ptr instMan, int instNum); }; } BambooTracker-0.6.5/BambooTracker/io/dmp_io.cpp000066400000000000000000000152071476276175200213600ustar00rootroot00000000000000/* * Copyright (C) 2020-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "dmp_io.hpp" #include "file_io_error.hpp" #include "io_utils.hpp" namespace io { DmpIO::DmpIO() : AbstractInstrumentIO("dmp", "DefleMask preset", true, false) {} AbstractInstrument* DmpIO::load(const BinaryContainer& ctr, const std::string& fileName, std::weak_ptr instMan, int instNum) const { std::shared_ptr instManLocked = instMan.lock(); size_t csr = 0; uint8_t insType = 1; // default to FM uint8_t fileVersion = ctr.readUint8(csr++); if (fileVersion == 0) { // older, unversioned dmp if (ctr.size() != 49) throw FileCorruptionError(FileType::Inst, csr); } else { if (fileVersion < 9) throw FileCorruptionError(FileType::Inst, csr); if (fileVersion == 9 && ctr.size() != 51) { // make sure it's not for that discontinued chip throw FileCorruptionError(FileType::Inst, csr); } uint8_t system = 2; // default to genesis if (fileVersion >= 11) system = ctr.readUint8(csr++); if (system != 2 && system != 3 && system != 8 && system != 9) { // genesis, sms, arcade and neo geo only throw FileCorruptionError(FileType::Inst, csr); } insType = ctr.readUint8(csr++); } AbstractInstrument* inst = nullptr; switch (insType) { case 0x00: // SSG { inst = new InstrumentSSG(instNum, fileName, instManLocked.get()); auto ssg = dynamic_cast(inst); uint8_t envSize = ctr.readUint8(csr++); if (envSize > 0) { int idx = instManLocked->findFirstAssignableEnvelopeSSG(); if (idx < 0) throw FileCorruptionError(FileType::Inst, csr); ssg->setEnvelopeEnabled(true); ssg->setEnvelopeNumber(idx); for (uint8_t l = 0; l < envSize; ++l) { int data = ctr.readInt32(csr); // compensate SN76489's envelope step of 2dB to SSG's 3dB if (data > 0) data = 15 - (15 - data) * 2 / 3; csr += 4; if (l == 0) instManLocked->setEnvelopeSSGSequenceData(idx, 0, SSGEnvelopeUnit::makeOnlyDataUnit(data)); else instManLocked->addEnvelopeSSGSequenceData(idx, SSGEnvelopeUnit::makeOnlyDataUnit(data)); } int8_t loop = ctr.readInt8(csr++); if (loop >= 0) instManLocked->addEnvelopeSSGLoop(idx, InstrumentSequenceLoop(loop, envSize - 1)); } uint8_t arpSize = ctr.readUint8(csr++); if (arpSize > 0) { int idx = instManLocked->findFirstAssignableArpeggioSSG(); if (idx < 0) throw FileCorruptionError(FileType::Inst, csr); ssg->setArpeggioEnabled(true); ssg->setArpeggioNumber(idx); uint8_t arpType = ctr.readUint8(csr + arpSize * 4 + 1); if (arpType == 1) instManLocked->setArpeggioSSGType(idx, SequenceType::FixedSequence); for (uint8_t l = 0; l < arpSize; ++l) { int data = ctr.readInt32(csr) + 36; csr += 4; if (arpType == 1) data -= 24; if (l == 0) instManLocked->setArpeggioSSGSequenceData(idx, 0, data); else instManLocked->addArpeggioSSGSequenceData(idx, data); } int8_t loop = ctr.readInt8(csr++); if (loop >= 0) instManLocked->addArpeggioSSGLoop(idx, InstrumentSequenceLoop(loop, arpSize - 1)); } break; } case 0x01: // FM { int envIdx = instManLocked->findFirstAssignableEnvelopeFM(); if (envIdx < 0) throw FileCorruptionError(FileType::Inst, csr); inst = new InstrumentFM(instNum, fileName, instManLocked.get()); auto fm = dynamic_cast(inst); fm->setEnvelopeNumber(envIdx); if (fileVersion == 9) csr++; // skip version 9's total operators field uint8_t pms = ctr.readUint8(csr++); instManLocked->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::FB, ctr.readUint8(csr++)); instManLocked->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AL, ctr.readUint8(csr++)); uint8_t ams = ctr.readUint8(csr++); uint8_t am[4] = {}; for (const int op : { 0, 2, 1, 3 }) { auto& params = FM_OP_PARAMS[op]; instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::ML), ctr.readUint8(csr++)); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::TL), ctr.readUint8(csr++)); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::AR), ctr.readUint8(csr++)); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::DR), ctr.readUint8(csr++)); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::SL), ctr.readUint8(csr++)); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::RR), ctr.readUint8(csr++)); am[op] = ctr.readUint8(csr++); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::KS), ctr.readUint8(csr++)); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::DT), convertDtFromDmpTfiVgi(ctr.readUint8(csr++) & 15)); // mask out OPM's DT2 instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::SR), ctr.readUint8(csr++)); int ssgeg1 = ctr.readUint8(csr++); ssgeg1 = ssgeg1 & 8 ? ssgeg1 & 7 : -1; instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::SSGEG), ssgeg1); } if (pms || ams) { int lfoIdx = instManLocked->findFirstAssignableLFOFM(); if (lfoIdx < 0) throw FileCorruptionError(FileType::Inst, csr); fm->setLFOEnabled(true); fm->setLFONumber(lfoIdx); instManLocked->setLFOFMParameter(lfoIdx, FMLFOParameter::PMS, pms); instManLocked->setLFOFMParameter(lfoIdx, FMLFOParameter::AMS, ams); instManLocked->setLFOFMParameter(lfoIdx, FMLFOParameter::AM1, am[0]); instManLocked->setLFOFMParameter(lfoIdx, FMLFOParameter::AM2, am[1]); instManLocked->setLFOFMParameter(lfoIdx, FMLFOParameter::AM3, am[2]); instManLocked->setLFOFMParameter(lfoIdx, FMLFOParameter::AM4, am[3]); } break; } } return inst; } } BambooTracker-0.6.5/BambooTracker/io/dmp_io.hpp000066400000000000000000000026101476276175200213570ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "instrument_io.hpp" namespace io { class DmpIO final : public AbstractInstrumentIO { public: DmpIO(); AbstractInstrument* load(const BinaryContainer& ctr, const std::string& fileName, std::weak_ptr instMan, int instNum) const override; }; } BambooTracker-0.6.5/BambooTracker/io/export_io.cpp000066400000000000000000000220031476276175200221110ustar00rootroot00000000000000/* * Copyright (C) 2019-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "export_io.hpp" #include "binary_container.hpp" #include "io_file_type.hpp" #include "file_io_error.hpp" namespace io { void writeVgm(BinaryContainer& container, int target, const std::vector& samples, uint32_t clock, uint32_t rate, bool loopFlag, uint32_t loopPoint, uint32_t loopSamples, uint32_t totalSamples, const GD3Tag* tag, const VgmMix* mix) { uint32_t tagLen = 0; uint32_t tagDataLen = 0; if (tag) { tagDataLen = tag->trackNameEn.length() + tag->trackNameJp.length() + tag->gameNameEn.length() + tag->gameNameJp.length() + tag->systemNameEn.length() + tag->systemNameJp.length() + tag->authorEn.length() + tag->authorJp.length() + tag->releaseDate.length() + tag->vgmCreator.length() + tag->notes.length(); tagLen = 12 + tagDataLen; } uint32_t extraLen = mix ? 17 : 0; // Header // 0x00: "Vgm " ident uint8_t header[0x100] = {'V', 'g', 'm', ' '}; // 0x04: EOF offset uint32_t offset = 0x100+ extraLen + samples.size() + 1 + tagLen - 4; *reinterpret_cast(header + 0x04) = offset; // 0x08: Version [v1.71] uint32_t version = 0x171; *reinterpret_cast(header + 0x08) = version; // 0x14: GD3 offset uint32_t gd3Offset = tag ? (0x100 + extraLen + samples.size() + 1 - 0x14) : 0; *reinterpret_cast(header + 0x14) = gd3Offset; // 0x18: Total # samples *reinterpret_cast(header + 0x18) = totalSamples; // 0x1c: Loop offset uint32_t loopOffset = loopFlag ? (loopPoint + 0x100 + extraLen - 0x1c) : 0; *reinterpret_cast(header + 0x1c) = loopOffset; // 0x20: Loop # samples uint32_t loopSamps = loopFlag ? loopSamples : 0; *reinterpret_cast(header + 0x20) = loopSamps; // 0x24: Rate *reinterpret_cast(header + 0x24) = rate; // 0x34: VGM data offset *reinterpret_cast(header + 0x34) = 0x100 + extraLen - 0x34; switch (target & Export_FmMask) { default: case Export_YM2608: // 0x48: YM2608 clock *reinterpret_cast(header + 0x48) = clock; break; case Export_YM2612: // 0x2c: YM2612 clock *reinterpret_cast(header + 0x2c) = clock; break; case Export_YM2203: // 0x44: YM2203 clock *reinterpret_cast(header + 0x44) = clock / 2; break; case Export_YM2610B: // 0x4c: YM2610/B clock *reinterpret_cast(header + 0x4c) = clock; break; case Export_NoneFm: break; } switch (target & Export_SsgMask) { case Export_InternalSsg: break; default: // 0x74: AY8910 clock *reinterpret_cast(header + 0x74) = clock / 4; // 0x78: AY8910 chip type if ((target & Export_SsgMask) == Export_YM2149Psg) *reinterpret_cast(header + 0x78) = 0x10; // 0x79: AY8910 flags *reinterpret_cast(header + 0x79) = 0x01; break; } // 0xbc: Extra header offset *reinterpret_cast(header + 0xbc) = mix ? (0x100 - 0xbc) : 0; container.appendArray(header, 0x100); // Extra header if (mix) { // Extra header size container.appendUint32(12); // Chip clock offset container.appendUint32(0); // Chip volime offset container.appendUint32(4); // Entry count container.appendUint8(1); // Set SSG volume of YM2608 container.appendUint8(0x87); // Flags container.appendUint8(0); // Volume container.appendUint16(mix->ssgMultiplier); } // Commands container.appendVector(samples); container.appendUint8(0x66); // End // GD3 tag if (tag) { // "Gd3 " ident container.appendString("Gd3 "); // Version [v1.00] uint32_t gd3Version = 0x100; container.appendUint32(gd3Version); // Data size container.appendUint32(tagDataLen); // Track name in english container.appendString(tag->trackNameEn); // Track name in japanes container.appendString(tag->trackNameJp); // Game name in english container.appendString(tag->gameNameEn); // Game name in japanese container.appendString(tag->gameNameJp); // System name in english container.appendString(tag->systemNameEn); // System name in japanese container.appendString(tag->systemNameJp); // Track author in english container.appendString(tag->authorEn); // Track author in japanese container.appendString(tag->authorJp); // Release date container.appendString(tag->releaseDate); // VGM creator container.appendString(tag->vgmCreator); // Notes container.appendString(tag->notes); } } void writeS98(BinaryContainer& container, int target, const std::vector& samples, uint32_t clock, uint32_t rate, bool loopFlag, uint32_t loopPoint, bool tagEnabled, const S98Tag& tag) { // Header // 0x00: Magic "S98" container.appendString("S98"); // 0x03: Format version 3 uint8_t version = 0x33; container.appendUint8(version); // 0x04: Timer info (sync numerator) uint32_t timeNum = 1; container.appendUint32(timeNum); // 0x08: Timer info 2 (sync denominator) container.appendUint32(rate); // 0x0c: Deprecated uint32_t zero = 0; container.appendUint32(zero); // 0x10: Tag offset uint32_t tagOffset = tagEnabled ? (0x80 + samples.size() + 1) : 0; container.appendUint32(tagOffset); // 0x14: Dump data offset uint32_t dumpOffset = 0x80; container.appendUint32(dumpOffset); // 0x18: Loop offset uint32_t loopOffset = loopFlag ? (0x80 + loopPoint) : 0; container.appendUint32(loopOffset); // 0x1c: Device count uint32_t deviceCnt = 0; if ((target & Export_FmMask) != Export_NoneFm) ++deviceCnt; if ((target & Export_SsgMask) != Export_InternalSsg) ++deviceCnt; container.appendUint32(deviceCnt); if ((target & Export_FmMask) != Export_NoneFm) { // 0x20-0x2f: Device info (if NoneFM, skipped) // 0x20: Device type uint32_t deviceType; uint32_t deviceClock; switch (target & Export_FmMask) { default: case Export_YM2608: deviceType = 4; // OPNA deviceClock = clock; break; case Export_YM2612: deviceType = 3; // OPN2 deviceClock = clock; break; case Export_YM2203: deviceType = 2; // OPN deviceClock = clock / 2; break; } container.appendUint32(deviceType); // 0x24: Clock container.appendUint32(deviceClock); // 0x28: Pan (Unused) container.appendUint32(zero); // 0x2c: Reserved container.appendUint32(zero); } if ((target & Export_SsgMask) != Export_InternalSsg) { // 0x30-0x3f: Device info // 0x30: Device type uint32_t subdeviceType; uint32_t subdeviceClock; switch (target & Export_SsgMask) { default: case Export_AY8910Psg: subdeviceType = 15; // PSG (AY-3-8910) subdeviceClock = clock / 4; break; case Export_YM2149Psg: subdeviceType = 1; // PSG (YM 2149) subdeviceClock = clock / 2; break; } container.appendUint32(subdeviceType); // 0x34: Clock container.appendUint32(subdeviceClock); // 0x38: Pan (Unused) container.appendUint32(zero); // 0x3c: Reserved container.appendUint32(zero); } // 0x??-0x7f: Unused for (uint32_t i = 0; i < (24 - 4 * deviceCnt); ++i) container.appendUint32(zero); // Commands container.appendVector(samples); container.appendUint8(0xfd); // End // GD3 tag if (tagEnabled) { // Tag ident container.appendString("[S98]"); // BOM uint8_t bom[] = { 0xef, 0xbb, 0xbf }; container.appendArray(bom, 3); uint8_t nl = 0x0a; // Title container.appendString("title=" + tag.title); container.appendUint8(nl); // Artist container.appendString("artist=" + tag.artist); container.appendUint8(nl); // Game container.appendString("game=" + tag.game); container.appendUint8(nl); // Year container.appendString("year=" + tag.year); container.appendUint8(nl); // Genre container.appendString("genre=" + tag.genre); container.appendUint8(nl); // Comment container.appendString("comment=" + tag.comment); container.appendUint8(nl); // Copyright container.appendString("copyright=" + tag.copyright); container.appendUint8(nl); // S98by container.appendString("s98by=" + tag.s98by); container.appendUint8(nl); // System container.appendString("system=" + tag.system); container.appendUint8(nl); uint8_t end = 0; container.appendUint8(end); } } } BambooTracker-0.6.5/BambooTracker/io/export_io.hpp000066400000000000000000000055451476276175200221320ustar00rootroot00000000000000/* * Copyright (C) 2019-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include namespace io { class BinaryContainer; // VGM ---------- struct GD3Tag { std::string trackNameEn, trackNameJp; std::string gameNameEn, gameNameJp; std::string systemNameEn, systemNameJp; std::string authorEn, authorJp; std::string releaseDate; std::string vgmCreator; std::string notes; }; /** * @brief Helper struct of SSG volume level for VGM export. */ struct VgmMix { const uint16_t ssgMultiplier; VgmMix(double fmLevel, double ssgLevel, double gain) : ssgMultiplier(static_cast(std::pow(10.0, (ssgLevel + gain - fmLevel) / 20.0) * 0x100) | 0x8000) {} }; void writeVgm(BinaryContainer& container, int target, const std::vector& samples, uint32_t clock, uint32_t rate, bool loopFlag, uint32_t loopPoint, uint32_t loopSamples, uint32_t totalSamples, const GD3Tag* tag, const VgmMix* mix); // S98 ---------- struct S98Tag { std::string title; std::string artist; std::string game; std::string year; std::string genre; std::string comment; std::string copyright; std::string s98by; std::string system; }; void writeS98(BinaryContainer& container, int target, const std::vector& samples, uint32_t clock, uint32_t rate, bool loopFlag, uint32_t loopPoint, bool tagEnabled, const S98Tag& tag); enum ExportTargetFlag { /* target bits 0-3 : FM type */ Export_NoneFm = 0, Export_YM2608 = 1, Export_YM2612 = 2, Export_YM2203 = 4, Export_YM2610B = 8, Export_FmMask = Export_NoneFm | Export_YM2608 | Export_YM2612 | Export_YM2203 | Export_YM2610B, /* target bit 4-5 : SSG type */ Export_InternalSsg = 0, Export_AY8910Psg = 16, Export_YM2149Psg = 32, Export_SsgMask = Export_InternalSsg|Export_AY8910Psg|Export_YM2149Psg, }; } BambooTracker-0.6.5/BambooTracker/io/ff_io.cpp000066400000000000000000000104451476276175200211720ustar00rootroot00000000000000/* * Copyright (C) 2020-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "ff_io.hpp" #include #include #include "instrument.hpp" #include "file_io_error.hpp" namespace io { FfIO::FfIO() : AbstractBankIO("ff", "PMD FF", true, false) {} AbstractBank* FfIO::load(const BinaryContainer& ctr) const { size_t csr = 0; // File size check size_t ctrSize = ctr.size(); if (!ctrSize || ctrSize & 0x1f || ctrSize > 0x2000) throw FileCorruptionError(FileType::Bank, csr); std::vector ids; std::vector names; std::vector ctrs; int max = static_cast(ctrSize / 0x20); for (int i = 0; i < max; ++i) { BinaryContainer block = ctr.getSubcontainer(csr, 25); csr += 25; std::string name = ""; for (size_t j = 0; j < 7; ++j) { if (char c = ctr.readChar(csr + j)) name += c; else break; } csr += 7; // Empty if (std::all_of(block.begin(), block.end(), [](const char c) { return c == 0; }) && name.empty()) continue; ids.push_back(i); names.push_back(name); ctrs.push_back(block); } return new FfBank(ids, names, ctrs); } AbstractInstrument* FfIO::loadInstrument(const BinaryContainer& instCtr, const std::string& name, std::weak_ptr instMan, int instNum) { std::shared_ptr instManLocked = instMan.lock(); int envIdx = instManLocked->findFirstAssignableEnvelopeFM(); if (envIdx < 0) throw FileCorruptionError(FileType::Bank, 0); InstrumentFM* fm = new InstrumentFM(instNum, name, instManLocked.get()); fm->setEnvelopeNumber(envIdx); uint8_t tmp; size_t initCsr[] = { 0, 2, 1, 3 }; for (int op = 0; op < 4; ++op) { size_t csr = initCsr[op]; auto& params = FM_OP_PARAMS[op]; tmp = instCtr.readUint8(csr); uint8_t ssgeg = (tmp & 0x80) ? 8 : 0; instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::DT), (tmp & 0x70) >> 4); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::ML), tmp & 0x0f); csr += 4; tmp = instCtr.readUint8(csr); if (tmp & 0x80) ssgeg |= 4; instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::TL), tmp & 0x7f); csr += 4; tmp = instCtr.readUint8(csr); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::KS), tmp >> 6); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::AR), tmp & 0x1f); csr += 4; tmp = instCtr.readUint8(csr); ssgeg |= ((tmp & 0x60) >> 5); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::SSGEG), (ssgeg & 8) ? ssgeg & 7 : -1); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::DR), tmp & 0x1f); csr += 4; tmp = instCtr.readUint8(csr); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::SR), tmp); csr += 4; tmp = instCtr.readUint8(csr); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::SL), tmp >> 4); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::RR), tmp & 0x0f); } tmp = instCtr.readUint8(24); instManLocked->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::FB, tmp >> 3); instManLocked->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AL, tmp & 0x07); return fm; } } BambooTracker-0.6.5/BambooTracker/io/ff_io.hpp000066400000000000000000000027341476276175200212010ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "bank_io.hpp" namespace io { class FfIO final : public AbstractBankIO { public: FfIO(); AbstractBank* load(const BinaryContainer& ctr) const override; static AbstractInstrument* loadInstrument(const BinaryContainer& instCtr, const std::string& name, std::weak_ptr instMan, int instNum); }; } BambooTracker-0.6.5/BambooTracker/io/file_io_error.hpp000066400000000000000000000042551476276175200227360ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include "io_file_type.hpp" namespace io { class FileIOError : public std::runtime_error { protected: const FileType ftype_; FileIOError(const std::string& text, const FileType ftype) : std::runtime_error(text), ftype_(ftype) {} public: inline FileType fileType() const { return ftype_; } }; class FileNotExistError : public FileIOError { FileNotExistError(const FileType type) : FileIOError("File not exist error", type) {} }; class FileUnsupportedError : public FileIOError { public: FileUnsupportedError(const FileType type) : FileIOError("File unsupported error", type) {} }; class FileVersionError : public FileIOError { public: FileVersionError(const FileType type) : FileIOError("File version error", type) {} }; class FileCorruptionError : public FileIOError { private: size_t pos_; public: FileCorruptionError(const FileType type, size_t pos, const std::string& desc = "File corruption error") : FileIOError(desc, type), pos_(pos) {} inline size_t position() const { return pos_; } }; } BambooTracker-0.6.5/BambooTracker/io/ins_io.cpp000066400000000000000000000070141476276175200213660ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "ins_io.hpp" #include "file_io_error.hpp" namespace io { InsIO::InsIO() : AbstractInstrumentIO("ins", "MVSTracker instrument", true, false) {} AbstractInstrument* InsIO::load(const BinaryContainer& ctr, const std::string& fileName, std::weak_ptr instMan, int instNum) const { (void)fileName; std::shared_ptr instManLocked = instMan.lock(); size_t csr = 0; if (ctr.readString(csr, 4).compare("MVSI") != 0) throw FileCorruptionError(FileType::Inst, csr); csr += 4; /*uint8_t fileVersion = */std::stoi(ctr.readString(csr++, 1)); size_t nameCsr = 0; while (ctr.readChar(nameCsr++) != '\0') ; std::string name = ctr.readString(csr, nameCsr - csr); csr = nameCsr; if (ctr.size() - csr != 25) throw FileCorruptionError(FileType::Inst, csr); int envIdx = instManLocked->findFirstAssignableEnvelopeFM(); if (envIdx < 0) throw FileCorruptionError(FileType::Inst, csr); InstrumentFM* inst = new InstrumentFM(instNum, name, instManLocked.get()); inst->setEnvelopeNumber(envIdx); uint8_t tmp; size_t initCsr[] = { 0, 1, 2, 3 }; for (int op = 0; op < 4; ++op) { size_t pcsr = csr + initCsr[op]; auto& params = FM_OP_PARAMS[op]; tmp = ctr.readUint8(pcsr); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::ML), 0x0f & tmp); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::DT), tmp >> 4); pcsr += 4; instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::TL), ctr.readUint8(pcsr)); pcsr += 4; tmp = ctr.readUint8(pcsr); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::AR), 0x3f & tmp); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::KS), tmp >> 6); pcsr += 4; instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::DR), ctr.readUint8(pcsr)); pcsr += 4; instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::SR), ctr.readUint8(pcsr)); pcsr += 4; tmp = ctr.readUint8(pcsr); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::RR), 0x0f & tmp); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::SL), tmp >> 4); } csr += 24; tmp = ctr.readUint8(csr); instManLocked->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AL, 0x07 & tmp); instManLocked->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::FB, tmp >> 3); return inst; } } BambooTracker-0.6.5/BambooTracker/io/ins_io.hpp000066400000000000000000000026101476276175200213700ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "instrument_io.hpp" namespace io { class InsIO final : public AbstractInstrumentIO { public: InsIO(); AbstractInstrument* load(const BinaryContainer& ctr, const std::string& fileName, std::weak_ptr instMan, int instNum) const override; }; } BambooTracker-0.6.5/BambooTracker/io/instrument_io.cpp000066400000000000000000000056301476276175200230070ustar00rootroot00000000000000/* * Copyright (C) 2019-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "instrument_io.hpp" #include "file_io_error.hpp" #include "bti_io.hpp" #include "dmp_io.hpp" #include "tfi_io.hpp" #include "vgi_io.hpp" #include "opni_io.hpp" #include "y12_io.hpp" #include "ins_io.hpp" #include "raw_adpcm_io.hpp" namespace io { AbstractInstrument* AbstractInstrumentIO::load(const BinaryContainer& ctr, const std::string& fileName, std::weak_ptr instMan, int instNum) const { (void)ctr; (void)fileName; (void)instMan; (void)instNum; throw FileUnsupportedError(FileType::Inst); } void AbstractInstrumentIO::save(BinaryContainer& ctr, const std::weak_ptr instMan, int instNum) const { (void)ctr; (void)instMan; (void)instNum; throw FileUnsupportedError(FileType::Inst); } //------------------------------------------------------------ std::unique_ptr InstrumentIO::instance_; InstrumentIO::InstrumentIO() { handler_.add(new BtiIO); handler_.add(new DmpIO); handler_.add(new TfiIO); handler_.add(new VgiIO); handler_.add(new OpniIO); handler_.add(new Y12IO); handler_.add(new InsIO); handler_.add(new RawAdpcmIO); } InstrumentIO& InstrumentIO::getInstance() { if (!instance_) instance_.reset(new InstrumentIO); return *instance_; } void InstrumentIO::saveInstrument(BinaryContainer& ctr, const std::weak_ptr instMan, int instNum) { handler_.at("bti")->save(ctr, instMan, instNum); } AbstractInstrument* InstrumentIO::loadInstrument(const BinaryContainer& ctr, const std::string& path, std::weak_ptr instMan, int instNum) { size_t fnpos = path.find_last_of("/"); std::string name = path.substr(fnpos + 1, path.find_last_of(".") - fnpos - 1); return handler_.at(getExtension(path))->load(ctr, name, instMan, instNum); } } BambooTracker-0.6.5/BambooTracker/io/instrument_io.hpp000066400000000000000000000061041476276175200230110ustar00rootroot00000000000000/* * Copyright (C) 2019-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include "instruments_manager.hpp" #include "binary_container.hpp" #include "io_file_type.hpp" #include "io_utils.hpp" namespace io { class AbstractInstrumentIO { public: AbstractInstrumentIO(const std::string& ext, const std::string& desc, bool loadable, bool savable) : ext_(ext), desc_(desc), loadable_(loadable), savable_(savable) {} virtual ~AbstractInstrumentIO() = default; virtual AbstractInstrument* load(const BinaryContainer& ctr, const std::string& fileName, std::weak_ptr instMan, int instNum) const; virtual void save(BinaryContainer& ctr, const std::weak_ptr instMan, int instNum) const; inline std::string getExtension() const noexcept { return ext_; } inline std::string getFilterText() const { return desc_ + " (*." + ext_ + ")"; } inline bool isLoadable() const noexcept { return loadable_; } inline bool isSavable() const noexcept { return savable_; } private: const std::string ext_, desc_; bool loadable_, savable_; }; class InstrumentIO { public: static InstrumentIO& getInstance(); void saveInstrument(BinaryContainer& ctr, const std::weak_ptr instMan, int instNum); AbstractInstrument* loadInstrument(const BinaryContainer& ctr, const std::string& path, std::weak_ptr instMan, int instNum); inline bool testLoadableFormat(const std::string& ext) const { return handler_.testLoadableExtension(ext); } inline bool testSavableFormat(const std::string& ext) const { return handler_.testSavableExtension(ext); } inline std::vector getLoadFilter() const { return handler_.getLoadFilterList(); } inline std::vector getSaveFilter() const { return handler_.getSaveFilterList(); } private: InstrumentIO(); static std::unique_ptr instance_; FileIOHandlerMap handler_; }; } BambooTracker-0.6.5/BambooTracker/io/io_file_type.hpp000066400000000000000000000023071476276175200225620ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once namespace io { enum class FileType { Mod, Inst, WAV, VGM, Bank, S98, Unknown }; } BambooTracker-0.6.5/BambooTracker/io/io_utils.cpp000066400000000000000000000120741476276175200217370ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "io_utils.hpp" #include #include "binary_container.hpp" #include "instrument/envelope_fm.hpp" #include "instrument/instrument_property_defs.hpp" namespace io { const std::unordered_map FM_OP_PARAMS[4] = { { { FMOperatorParameter::AR, FMEnvelopeParameter::AR1 }, { FMOperatorParameter::DR, FMEnvelopeParameter::DR1 }, { FMOperatorParameter::SR, FMEnvelopeParameter::SR1 }, { FMOperatorParameter::RR, FMEnvelopeParameter::RR1 }, { FMOperatorParameter::SL, FMEnvelopeParameter::SL1 }, { FMOperatorParameter::TL, FMEnvelopeParameter::TL1 }, { FMOperatorParameter::KS, FMEnvelopeParameter::KS1 }, { FMOperatorParameter::ML, FMEnvelopeParameter::ML1 }, { FMOperatorParameter::DT, FMEnvelopeParameter::DT1 }, { FMOperatorParameter::SSGEG, FMEnvelopeParameter::SSGEG1 } }, { { FMOperatorParameter::AR, FMEnvelopeParameter::AR2 }, { FMOperatorParameter::DR, FMEnvelopeParameter::DR2 }, { FMOperatorParameter::SR, FMEnvelopeParameter::SR2 }, { FMOperatorParameter::RR, FMEnvelopeParameter::RR2 }, { FMOperatorParameter::SL, FMEnvelopeParameter::SL2 }, { FMOperatorParameter::TL, FMEnvelopeParameter::TL2 }, { FMOperatorParameter::KS, FMEnvelopeParameter::KS2 }, { FMOperatorParameter::ML, FMEnvelopeParameter::ML2 }, { FMOperatorParameter::DT, FMEnvelopeParameter::DT2 }, { FMOperatorParameter::SSGEG, FMEnvelopeParameter::SSGEG2 } }, { { FMOperatorParameter::AR, FMEnvelopeParameter::AR3 }, { FMOperatorParameter::DR, FMEnvelopeParameter::DR3 }, { FMOperatorParameter::SR, FMEnvelopeParameter::SR3 }, { FMOperatorParameter::RR, FMEnvelopeParameter::RR3 }, { FMOperatorParameter::SL, FMEnvelopeParameter::SL3 }, { FMOperatorParameter::TL, FMEnvelopeParameter::TL3 }, { FMOperatorParameter::KS, FMEnvelopeParameter::KS3 }, { FMOperatorParameter::ML, FMEnvelopeParameter::ML3 }, { FMOperatorParameter::DT, FMEnvelopeParameter::DT3 }, { FMOperatorParameter::SSGEG, FMEnvelopeParameter::SSGEG3 } }, { { FMOperatorParameter::AR, FMEnvelopeParameter::AR4 }, { FMOperatorParameter::DR, FMEnvelopeParameter::DR4 }, { FMOperatorParameter::SR, FMEnvelopeParameter::SR4 }, { FMOperatorParameter::RR, FMEnvelopeParameter::RR4 }, { FMOperatorParameter::SL, FMEnvelopeParameter::SL4 }, { FMOperatorParameter::TL, FMEnvelopeParameter::TL4 }, { FMOperatorParameter::KS, FMEnvelopeParameter::KS4 }, { FMOperatorParameter::ML, FMEnvelopeParameter::ML4 }, { FMOperatorParameter::DT, FMEnvelopeParameter::DT4 }, { FMOperatorParameter::SSGEG, FMEnvelopeParameter::SSGEG4 } }, }; const FMEnvelopeParameter FM_OPSEQ_PARAMS[38] = { FMEnvelopeParameter::AL, FMEnvelopeParameter::FB, FMEnvelopeParameter::AR1, FMEnvelopeParameter::DR1, FMEnvelopeParameter::SR1, FMEnvelopeParameter::RR1, FMEnvelopeParameter::SL1, FMEnvelopeParameter::TL1, FMEnvelopeParameter::KS1, FMEnvelopeParameter::ML1, FMEnvelopeParameter::DT1, FMEnvelopeParameter::AR2, FMEnvelopeParameter::DR2, FMEnvelopeParameter::SR2, FMEnvelopeParameter::RR2, FMEnvelopeParameter::SL2, FMEnvelopeParameter::TL2, FMEnvelopeParameter::KS2, FMEnvelopeParameter::ML2, FMEnvelopeParameter::DT2, FMEnvelopeParameter::AR3, FMEnvelopeParameter::DR3, FMEnvelopeParameter::SR3, FMEnvelopeParameter::RR3, FMEnvelopeParameter::SL3, FMEnvelopeParameter::TL3, FMEnvelopeParameter::KS3, FMEnvelopeParameter::ML3, FMEnvelopeParameter::DT3, FMEnvelopeParameter::AR4, FMEnvelopeParameter::DR4, FMEnvelopeParameter::SR4, FMEnvelopeParameter::RR4, FMEnvelopeParameter::SL4, FMEnvelopeParameter::TL4, FMEnvelopeParameter::KS4, FMEnvelopeParameter::ML4, FMEnvelopeParameter::DT4 }; const FMOperatorType FM_OP_TYPES[4] = { FMOperatorType::Op1, FMOperatorType::Op2, FMOperatorType::Op3, FMOperatorType::Op4 }; int convertDtFromDmpTfiVgi(int dt) { switch (dt) { case 0: return 7; case 1: return 6; case 2: return 5; case 3: return 0; case 4: return 1; case 5: return 2; case 6: return 3; case 7: return 3; default: throw std::out_of_range("Out of range dt"); } } } BambooTracker-0.6.5/BambooTracker/io/io_utils.hpp000066400000000000000000000064701476276175200217470ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include #include #include "enum_hash.hpp" enum class FMEnvelopeParameter; enum class FMOperatorType; namespace io { class BinaryContainer; template class FileIOHandlerMap { public: FileIOHandlerMap() { const std::string all = "All files (*)"; ldFilters_.push_back(all); svFilters_.push_back(all); } void add(T* handler) { const std::string ext = handler->getExtension(); map_[ext].reset(handler); const std::string filter = handler->getFilterText(); if (handler->isLoadable()) { ldFilters_.insert(ldFilters_.end() - 1, filter); if (ldFilters_.size() > 2) ldExts_ += " "; ldExts_ += ("*." + ext); } if (handler->isSavable()) { svFilters_.insert(svFilters_.end() - 1, filter); if (svFilters_.size() > 2) svExts_ += " "; svExts_ += ("*." + ext); } } bool containExtension(const std::string& ext) const { return map_.count(ext); } bool testLoadableExtension(const std::string& ext) const { return (map_.count(ext) && map_.at(ext)->isLoadable()); } bool testSavableExtension(const std::string& ext) const { return (map_.count(ext) && map_.at(ext)->isSavable()); } const std::unique_ptr& at(const std::string& ext) const { return map_.at(ext); } std::vector getLoadFilterList() const noexcept { std::vector filters = ldFilters_; filters.insert(filters.begin(), "All supported formats (" + ldExts_ + ")"); return filters; } std::vector getSaveFilterList() const noexcept { return svFilters_; } private: std::unordered_map> map_; std::vector ldFilters_, svFilters_; std::string ldExts_, svExts_; }; enum class FMOperatorParameter { AR, DR, SR, RR, SL, TL, KS, ML, DT, SSGEG }; extern const std::unordered_map FM_OP_PARAMS[4]; extern const FMEnvelopeParameter FM_OPSEQ_PARAMS[38]; extern const FMOperatorType FM_OP_TYPES[4]; inline std::string getExtension(const std::string& path) { std::string ext = path.substr(path.find_last_of(".") + 1); std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); return ext; } int convertDtFromDmpTfiVgi(int dt); } BambooTracker-0.6.5/BambooTracker/io/module_io.cpp000066400000000000000000000044241476276175200220640ustar00rootroot00000000000000/* * Copyright (C) 2019-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "module_io.hpp" #include "file_io_error.hpp" #include "btm_io.hpp" namespace io { void AbstractModuleIO::load(const BinaryContainer& ctr, std::weak_ptr mod, std::weak_ptr instMan) const { (void)ctr; (void)mod; (void)instMan; throw FileUnsupportedError(FileType::Mod); } void AbstractModuleIO::save(BinaryContainer& ctr, const std::weak_ptr mod, const std::weak_ptr instMan) const { (void)ctr; (void)mod; (void)instMan; throw FileUnsupportedError(FileType::Mod); } //------------------------------------------------------------ std::unique_ptr ModuleIO::instance_; ModuleIO::ModuleIO() { handler_.add(new BtmIO); } ModuleIO& ModuleIO::getInstance() { if (!instance_) instance_.reset(new ModuleIO); return *instance_; } void ModuleIO::saveModule(BinaryContainer& ctr, const std::weak_ptr mod, const std::weak_ptr instMan) { handler_.at("btm")->save(ctr, mod, instMan); } void ModuleIO::loadModule(const BinaryContainer& ctr, std::weak_ptr mod, std::weak_ptr instMan) { handler_.at("btm")->load(ctr, mod, instMan); } } BambooTracker-0.6.5/BambooTracker/io/module_io.hpp000066400000000000000000000057311476276175200220730ustar00rootroot00000000000000/* * Copyright (C) 2019-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include "module.hpp" #include "instruments_manager.hpp" #include "binary_container.hpp" #include "io_utils.hpp" namespace io { class AbstractModuleIO { public: AbstractModuleIO(const std::string& ext, const std::string& desc, bool loadable, bool savable) : ext_(ext), desc_(desc), loadable_(loadable), savable_(savable) {} virtual ~AbstractModuleIO() = default; virtual void load(const BinaryContainer& ctr, std::weak_ptr mod, std::weak_ptr instMan) const; virtual void save(BinaryContainer& ctr, const std::weak_ptr mod, const std::weak_ptr instMan) const; inline std::string getExtension() const noexcept { return ext_; } inline std::string getFilterText() const { return desc_ + " (*." + ext_ + ")"; } inline bool isLoadable() const noexcept { return loadable_; } inline bool isSavable() const noexcept { return savable_; } private: const std::string ext_, desc_; bool loadable_, savable_; }; class ModuleIO { public: static ModuleIO& getInstance(); void saveModule(BinaryContainer& ctr, const std::weak_ptr mod, const std::weak_ptr instMan); void loadModule(const BinaryContainer& ctr, std::weak_ptr mod, std::weak_ptr instMan); inline bool testLoadableFormat(const std::string& ext) const { return handler_.testLoadableExtension(ext); } inline bool testSavableFormat(const std::string& ext) const { return handler_.testSavableExtension(ext); } inline std::vector getLoadFilter() const { return handler_.getLoadFilterList(); } inline std::vector getSaveFilter() const { return handler_.getSaveFilterList(); } private: ModuleIO(); static std::unique_ptr instance_; FileIOHandlerMap handler_; }; } BambooTracker-0.6.5/BambooTracker/io/opni_io.cpp000066400000000000000000000115411476276175200215420ustar00rootroot00000000000000/* * Copyright (C) 2020-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "opni_io.hpp" #include "format/wopn_file.h" #include "file_io_error.hpp" #include "note.hpp" namespace io { OpniIO::OpniIO() : AbstractInstrumentIO("opni", "WOPN instrument", true, false) {} AbstractInstrument* OpniIO::load(const BinaryContainer& ctr, const std::string& fileName, std::weak_ptr instMan, int instNum) const { (void)fileName; OPNIFile opni; auto&& mem = ctr.toVector(); if (WOPN_LoadInstFromMem(&opni, mem.data(), mem.size()) != 0) throw FileCorruptionError(FileType::Inst, 0); return loadWOPNInstrument(opni.inst, instMan, instNum); } AbstractInstrument* OpniIO::loadWOPNInstrument(const WOPNInstrument &srcInst, std::weak_ptr instMan, int instNum) { std::shared_ptr instManLocked = instMan.lock(); int envIdx = instManLocked->findFirstAssignableEnvelopeFM(); if (envIdx < 0) throw FileCorruptionError(FileType::Bank, 0); const char *name = srcInst.inst_name; InstrumentFM* inst = new InstrumentFM(instNum, name, instManLocked.get()); inst->setEnvelopeNumber(envIdx); instManLocked->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AL, srcInst.fbalg & 7); instManLocked->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::FB, (srcInst.fbalg >> 3) & 7); const WOPNOperator *op[4] = { &srcInst.operators[0], &srcInst.operators[2], &srcInst.operators[1], &srcInst.operators[3], }; int am[4]; for (int n = 0; n < 4; ++n) { auto& params = FM_OP_PARAMS[n]; instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::ML), op[n]->dtfm_30 & 15); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::DT), (op[n]->dtfm_30 >> 4) & 7); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::TL), op[n]->level_40); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::KS), op[n]->rsatk_50 >> 6); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::AR), op[n]->rsatk_50 & 31); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::DR), op[n]->amdecay1_60 & 31); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::SR), op[n]->decay2_70 & 31); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::RR), op[n]->susrel_80 & 15); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::SL), op[n]->susrel_80 >> 4); int ssgeg = op[n]->ssgeg_90; ssgeg = ssgeg & 8 ? ssgeg & 7 : -1; instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::SSGEG), ssgeg); am[n] = op[n]->amdecay1_60 >> 7; } if (srcInst.lfosens != 0) { int lfoIdx = instManLocked->findFirstAssignableLFOFM(); if (lfoIdx < 0) throw FileCorruptionError(FileType::Bank, 0); inst->setLFOEnabled(true); inst->setLFONumber(lfoIdx); instManLocked->setLFOFMParameter(lfoIdx, FMLFOParameter::PMS, srcInst.lfosens & 7); instManLocked->setLFOFMParameter(lfoIdx, FMLFOParameter::AMS, (srcInst.lfosens >> 4) & 3); instManLocked->setLFOFMParameter(lfoIdx, FMLFOParameter::AM1, am[0]); instManLocked->setLFOFMParameter(lfoIdx, FMLFOParameter::AM2, am[1]); instManLocked->setLFOFMParameter(lfoIdx, FMLFOParameter::AM3, am[2]); instManLocked->setLFOFMParameter(lfoIdx, FMLFOParameter::AM4, am[3]); } if (srcInst.note_offset != 0) { int arpIdx = instManLocked->findFirstAssignableArpeggioFM(); if (arpIdx < 0) throw FileCorruptionError(FileType::Bank, 0); inst->setArpeggioEnabled(FMOperatorType::All, true); inst->setArpeggioNumber(FMOperatorType::All, arpIdx); instManLocked->setArpeggioFMSequenceData(arpIdx, 0, srcInst.note_offset + Note::DEFAULT_NOTE_NUM); instManLocked->setArpeggioFMType(arpIdx, SequenceType::AbsoluteSequence); } return inst; } } BambooTracker-0.6.5/BambooTracker/io/opni_io.hpp000066400000000000000000000031061476276175200215450ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "instrument_io.hpp" struct WOPNInstrument; namespace io { class OpniIO final : public AbstractInstrumentIO { public: OpniIO(); AbstractInstrument* load(const BinaryContainer& ctr, const std::string& fileName, std::weak_ptr instMan, int instNum) const override; static AbstractInstrument* loadWOPNInstrument(const WOPNInstrument &srcInst, std::weak_ptr instMan, int instNum); }; } BambooTracker-0.6.5/BambooTracker/io/p86_io.cpp000066400000000000000000000072161476276175200212160ustar00rootroot00000000000000/* * Copyright (C) 2021-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "p86_io.hpp" #include #include "instrument.hpp" #include "file_io_error.hpp" #include "chip/codec/ymb_codec.hpp" namespace io { namespace { inline uint32_t readUint24(const BinaryContainer& ctr, size_t offset) { return ctr.readUint8(offset) | (ctr.readUint8(offset + 1) << 8u) | (ctr.readUint8(offset + 2) << 16u); } } P86IO::P86IO() : AbstractBankIO("p86", "PMD P86", true, false) {} AbstractBank* P86IO::load(const BinaryContainer& ctr) const { using namespace std::literals::string_literals; std::string id = ctr.readString(0, 12); if (id != "PCM86 DATA\n\0"s) throw FileCorruptionError(FileType::Bank, 0); uint32_t size = readUint24(ctr, 13); if (size != ctr.size()) throw FileCorruptionError(FileType::Bank, 13); constexpr size_t SAMP_OFFS = 0x610; if (ctr.size() < SAMP_OFFS) throw FileCorruptionError(FileType::Bank, 0x10); std::vector ids; std::vector> samples; size_t globCsr = 0x10; size_t offs = 0; constexpr int MAX_CNT = 256; for (int i = 0; i < MAX_CNT; ++i) { uint32_t start = readUint24(ctr, globCsr); globCsr += 3; uint32_t len = readUint24(ctr, globCsr); globCsr += 3; if (len) { if (ids.empty()) offs = start; ids.push_back(i); std::vector&& smp = ctr.getSubcontainer(SAMP_OFFS + start - offs, len).toVector(); std::vector buf(smp.size()); std::transform(smp.begin(), smp.end(), buf.begin(), [](uint8_t v) { return static_cast(static_cast(v)) << 8; }); smp.resize((smp.size() + 1) / 2); smp.shrink_to_fit(); smp.back() = 0; // Clear last data codec::ymb_encode(buf.data(), smp.data(), buf.size()); samples.push_back(std::move(smp)); } } return new P86Bank(ids, samples); } AbstractInstrument* P86IO::loadInstrument(const std::vector& sample, std::weak_ptr instMan, int instNum) { std::shared_ptr instManLocked = instMan.lock(); int sampIdx = instManLocked->findFirstAssignableSampleADPCM(); if (sampIdx < 0) throw FileCorruptionError(FileType::Bank, 0); InstrumentADPCM* adpcm = new InstrumentADPCM(instNum, "", instManLocked.get()); adpcm->setSampleNumber(sampIdx); instManLocked->storeSampleADPCMRawSample(sampIdx, sample); instManLocked->setSampleADPCMRootKeyNumber(sampIdx, 67); // o5g instManLocked->setSampleADPCMRootDeltaN(sampIdx, 0x4a0d); // 16540Hz instManLocked->setSampleADPCMRepeatEnabled(sampIdx, false); instManLocked->setSampleADPCMRepeatrange(sampIdx, SampleRepeatRange(0, (sample.size() - 1) >> 5)); return adpcm; } } BambooTracker-0.6.5/BambooTracker/io/p86_io.hpp000066400000000000000000000026741476276175200212260ustar00rootroot00000000000000/* * Copyright (C) 2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "bank_io.hpp" namespace io { class P86IO final : public AbstractBankIO { public: P86IO(); AbstractBank* load(const BinaryContainer& ctr) const override; static AbstractInstrument* loadInstrument(const std::vector& sample, std::weak_ptr instMan, int instNum); }; } BambooTracker-0.6.5/BambooTracker/io/pmb_io.cpp000066400000000000000000000104331476276175200213520ustar00rootroot00000000000000/* * Copyright (C) 2022-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "pmb_io.hpp" #include #include #include "instrument.hpp" #include "file_io_error.hpp" #include "chip/codec/ymb_codec.hpp" namespace io { PmbIO::PmbIO() : AbstractBankIO("pmb", "FM Towns PMB", true, false) {} AbstractBank* PmbIO::load(const BinaryContainer& ctr) const { /* no identifier & weird header inconsistencies across samples, just don't bother with the header */ constexpr size_t HEADER_SIZE = 0x1008; if (ctr.size() < HEADER_SIZE) throw FileCorruptionError(FileType::Bank, 0); std::vector ids; std::vector names; std::vector> samples; size_t globCsr = HEADER_SIZE; constexpr int MAX_CNT = 32; for (int i = 0; i < MAX_CNT; ++i) { // cannot determine how many samples there will be, just stop when we've reached EOF if (globCsr == ctr.size()) break; std::string name = ctr.readString(globCsr, 8); globCsr += 8; // ignore sample ID, they may override each other or disagree with the ones in the header // uint32_t id = ctr.readUint32(globCsr); globCsr += 4; uint32_t len = ctr.readUint32(globCsr); globCsr += 4; // unknown data globCsr += 16; ids.push_back(i); names.push_back(name); std::vector&& smp = ctr.getSubcontainer(globCsr, len).toVector(); std::vector buf(smp.size()); std::transform(smp.begin(), smp.end(), buf.begin(), [globCsr](uint8_t v) { // first convert from RF5C68 encoding to more regular unsigned 8-bit // summarised from the datasheet: // 0x00 -> 0x80 (not mentioned in datasheet but seems to work like this in banks) // 0x01 - 0x7F -> 0x7F - 0x01 // 0x80 - 0xFE -> 0x80 - 0xFE (unchanged) // 0xFF -> trigger for jumping into loop. unsure if occurs in PMB banks, loops not handled by us anyway. error if encountered if (v == 0xFF) throw FileCorruptionError (FileType::Bank, globCsr); uint8_t regular = (v < 0x80) ? (0x80 - v) : v; // now convert this into the format ADPCM conversion requires uint16_t grown = static_cast(regular + 0x80) << 8; return *(reinterpret_cast (&grown)); }); smp.resize((smp.size() + 1) / 2); smp.shrink_to_fit(); smp.back() = 0; // Clear last data codec::ymb_encode(buf.data(), smp.data(), buf.size()); samples.push_back(std::move(smp)); globCsr += len; } return new PmbBank(ids, names, samples); } AbstractInstrument* PmbIO::loadInstrument(const std::vector& sample, std::string name, std::weak_ptr instMan, int instNum) { std::shared_ptr instManLocked = instMan.lock(); int sampIdx = instManLocked->findFirstAssignableSampleADPCM(); if (sampIdx < 0) throw FileCorruptionError(FileType::Bank, 0); InstrumentADPCM* adpcm = new InstrumentADPCM(instNum, name, instManLocked.get()); adpcm->setSampleNumber(sampIdx); instManLocked->storeSampleADPCMRawSample(sampIdx, sample); instManLocked->setSampleADPCMRootKeyNumber(sampIdx, 67); // o5g instManLocked->setSampleADPCMRootDeltaN(sampIdx, 0x49cd); // 16000Hz instManLocked->setSampleADPCMRepeatEnabled(sampIdx, false); instManLocked->setSampleADPCMRepeatrange(sampIdx, SampleRepeatRange(0, (sample.size() - 1) >> 5)); return adpcm; } } BambooTracker-0.6.5/BambooTracker/io/pmb_io.hpp000066400000000000000000000027321476276175200213620ustar00rootroot00000000000000/* * Copyright (C) 2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "bank_io.hpp" namespace io { class PmbIO final : public AbstractBankIO { public: PmbIO(); AbstractBank* load(const BinaryContainer& ctr) const override; static AbstractInstrument* loadInstrument(const std::vector& sample, std::string name, std::weak_ptr instMan, int instNum); }; } BambooTracker-0.6.5/BambooTracker/io/ppc_io.cpp000066400000000000000000000063441476276175200213640ustar00rootroot00000000000000/* * Copyright (C) 2020-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "ppc_io.hpp" #include #include "instrument.hpp" #include "file_io_error.hpp" namespace io { PpcIO::PpcIO() : AbstractBankIO("ppc", "PMD PPC", true, false) {} AbstractBank* PpcIO::load(const BinaryContainer& ctr) const { size_t globCsr = 0; if (ctr.readString(globCsr, 30) != "ADPCM DATA for PMD ver.4.4- ") throw FileCorruptionError(FileType::Bank, globCsr); globCsr += 30; uint16_t nextAddr = ctr.readUint16(globCsr); if ((nextAddr - 0x26u) * 0x20u + 0x420u != ctr.size()) // File size check throw FileCorruptionError(FileType::Bank, globCsr); globCsr += 2; size_t sampOffs = globCsr + 256 * 4; if (ctr.size() < sampOffs) throw FileCorruptionError(FileType::Bank, globCsr); std::vector ids; std::vector> samples; size_t offs = 0; constexpr int MAX_CNT = 256; for (int i = 0; i < MAX_CNT; ++i) { uint16_t start = ctr.readUint16(globCsr); globCsr += 2; uint16_t stop = ctr.readUint16(globCsr); globCsr += 2; if (start < stop) { if (ids.empty()) offs = start; ids.push_back(i); size_t st = sampOffs + static_cast((start - offs) << 5); size_t sampSize = std::min((stop + 1u - start) << 5, ctr.size() - st); samples.push_back(ctr.getSubcontainer(st, sampSize).toVector()); } } return new PpcBank(ids, samples); } AbstractInstrument* PpcIO::loadInstrument(const std::vector& sample, std::weak_ptr instMan, int instNum) { std::shared_ptr instManLocked = instMan.lock(); int sampIdx = instManLocked->findFirstAssignableSampleADPCM(); if (sampIdx < 0) throw FileCorruptionError(FileType::Bank, 0); InstrumentADPCM* adpcm = new InstrumentADPCM(instNum, "", instManLocked.get()); adpcm->setSampleNumber(sampIdx); instManLocked->storeSampleADPCMRawSample(sampIdx, sample); instManLocked->setSampleADPCMRootKeyNumber(sampIdx, 67); // o5g instManLocked->setSampleADPCMRootDeltaN(sampIdx, 0x49cd); // 16000Hz instManLocked->setSampleADPCMRepeatEnabled(sampIdx, false); instManLocked->setSampleADPCMRepeatrange(sampIdx, SampleRepeatRange(0, (sample.size() - 1) >> 5)); return adpcm; } } BambooTracker-0.6.5/BambooTracker/io/ppc_io.hpp000066400000000000000000000026741476276175200213730ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "bank_io.hpp" namespace io { class PpcIO final : public AbstractBankIO { public: PpcIO(); AbstractBank* load(const BinaryContainer& ctr) const override; static AbstractInstrument* loadInstrument(const std::vector& sample, std::weak_ptr instMan, int instNum); }; } BambooTracker-0.6.5/BambooTracker/io/pps_io.cpp000066400000000000000000000061241476276175200214000ustar00rootroot00000000000000/* * Copyright (C) 2021-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "pps_io.hpp" #include #include "instrument.hpp" #include "file_io_error.hpp" #include "chip/codec/ymb_codec.hpp" namespace io { PpsIO::PpsIO() : AbstractBankIO("pps", "PMD PPS", true, false) {} AbstractBank* PpsIO::load(const BinaryContainer& ctr) const { constexpr size_t SAMP_OFFS = 0x54; if (ctr.size() < SAMP_OFFS) throw FileCorruptionError(FileType::Bank, 0); std::vector ids; std::vector> samples; size_t globCsr = 0; constexpr int MAX_CNT = 14; for (int i = 0; i < MAX_CNT; ++i) { uint16_t start = ctr.readUint16(globCsr); globCsr += 2; uint16_t len = ctr.readUint16(globCsr); globCsr += 4; // Skip 2 bytes if (len) { ids.push_back(i); std::vector&& smp = ctr.getSubcontainer(start, len).toVector(); std::vector buf(smp.size() * 2); for (size_t i = 0; i < smp.size(); ++i) { uint8_t sample = smp[i]; buf[i] = (static_cast(sample >> 4) - 8) << 12; buf[i + 1] = (static_cast(sample & 0x0f) - 8) << 12; } codec::ymb_encode(buf.data(), smp.data(), buf.size()); samples.push_back(std::move(smp)); } } return new PpsBank(ids, samples); } AbstractInstrument* PpsIO::loadInstrument(const std::vector& sample, std::weak_ptr instMan, int instNum) { std::shared_ptr instManLocked = instMan.lock(); int sampIdx = instManLocked->findFirstAssignableSampleADPCM(); if (sampIdx < 0) throw FileCorruptionError(FileType::Bank, 0); InstrumentADPCM* adpcm = new InstrumentADPCM(instNum, "", instManLocked.get()); adpcm->setSampleNumber(sampIdx); instManLocked->storeSampleADPCMRawSample(sampIdx, sample); instManLocked->setSampleADPCMRootKeyNumber(sampIdx, 67); // o5g instManLocked->setSampleADPCMRootDeltaN(sampIdx, 0x49cd); // 16000Hz instManLocked->setSampleADPCMRepeatEnabled(sampIdx, false); instManLocked->setSampleADPCMRepeatrange(sampIdx, SampleRepeatRange(0, (sample.size() - 1) >> 5)); return adpcm; } } BambooTracker-0.6.5/BambooTracker/io/pps_io.hpp000066400000000000000000000026741476276175200214130ustar00rootroot00000000000000/* * Copyright (C) 2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "bank_io.hpp" namespace io { class PpsIO final : public AbstractBankIO { public: PpsIO(); AbstractBank* load(const BinaryContainer& ctr) const override; static AbstractInstrument* loadInstrument(const std::vector& sample, std::weak_ptr instMan, int instNum); }; } BambooTracker-0.6.5/BambooTracker/io/pvi_io.cpp000066400000000000000000000062571476276175200214030ustar00rootroot00000000000000/* * Copyright (C) 2020-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "pvi_io.hpp" #include #include "instrument.hpp" #include "file_io_error.hpp" namespace io { PviIO::PviIO() : AbstractBankIO("pvi", "FMP PVI", true, false) {} AbstractBank* PviIO::load(const BinaryContainer& ctr) const { std::string ident = ctr.readString(0, 4); if (ident != "PVI1" && ident != "PVI2") throw FileCorruptionError(FileType::Bank, 0); uint16_t deltaN = ctr.readUint16(8); uint8_t cnt = ctr.readUint8(11); constexpr size_t sampOffs = 0x10 + 128 * 4; if (ctr.size() < sampOffs) throw FileCorruptionError(FileType::Bank, 0x10); std::vector ids; std::vector> samples; size_t offs = 0; size_t addrPos = 0x10; for (size_t i = 0; i < cnt; ++i) { uint16_t start = ctr.readUint16(addrPos); addrPos += 2; uint16_t stop = ctr.readUint16(addrPos); addrPos += 2; if (start < stop) { if (ids.empty()) offs = start; ids.push_back(static_cast(i)); size_t st = sampOffs + static_cast((start - offs) << 5); size_t sampSize = std::min((stop + 1u - start) << 5, ctr.size() - st); samples.push_back(ctr.getSubcontainer(st, sampSize).toVector()); } } /* if (ids.size() != cnt) throw FileCorruptionError(FileType::Bank, 11); */ return new PviBank(ids, deltaN, samples); } AbstractInstrument* PviIO::loadInstrument(const std::vector& sample, uint16_t deltaN, std::weak_ptr instMan, int instNum) { std::shared_ptr instManLocked = instMan.lock(); int sampIdx = instManLocked->findFirstAssignableSampleADPCM(); if (sampIdx < 0) throw FileCorruptionError(FileType::Bank, 0); InstrumentADPCM* adpcm = new InstrumentADPCM(instNum, "", instManLocked.get()); adpcm->setSampleNumber(sampIdx); instManLocked->storeSampleADPCMRawSample(sampIdx, sample); instManLocked->setSampleADPCMRootKeyNumber(sampIdx, 60); // o5c instManLocked->setSampleADPCMRootDeltaN(sampIdx, deltaN); instManLocked->setSampleADPCMRepeatEnabled(sampIdx, false); instManLocked->setSampleADPCMRepeatrange(sampIdx, SampleRepeatRange(0, (sample.size() - 1) >> 5)); return adpcm; } } BambooTracker-0.6.5/BambooTracker/io/pvi_io.hpp000066400000000000000000000027221476276175200214010ustar00rootroot00000000000000/* * Copyright (C) 2020-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "bank_io.hpp" namespace io { class PviIO final : public AbstractBankIO { public: PviIO(); AbstractBank* load(const BinaryContainer& ctr) const override; static AbstractInstrument* loadInstrument(const std::vector& sample, uint16_t deltaN, std::weak_ptr instMan, int instNum); }; } BambooTracker-0.6.5/BambooTracker/io/pzi_io.cpp000066400000000000000000000074501476276175200214030ustar00rootroot00000000000000/* * Copyright (C) 2021-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "pzi_io.hpp" #include #include #include "instrument.hpp" #include "file_io_error.hpp" #include "chip/codec/ymb_codec.hpp" namespace io { PziIO::PziIO() : AbstractBankIO("pzi", "FMP PZI", true, false) {} AbstractBank* PziIO::load(const BinaryContainer& ctr) const { std::string ident = ctr.readString(0, 4); if (ident != "PZI0" && ident != "PZI1") throw FileCorruptionError(FileType::Bank, 0); constexpr size_t SAMP_OFFS = 0x920; if (ctr.size() < SAMP_OFFS) throw FileCorruptionError(FileType::Bank, 0x20); std::vector ids; std::vector> samples; std::vector isRepeatedList; std::vector deltaNs; size_t globCsr = 0x20; constexpr int MAX_CNT = 128; for (int i = 0; i < MAX_CNT; ++i) { uint32_t start = ctr.readUint32(globCsr); globCsr += 4; uint32_t len = ctr.readUint32(globCsr); globCsr += 4; uint32_t loopStart = ctr.readUint32(globCsr); globCsr += 4; uint32_t loopEnd = ctr.readUint32(globCsr); globCsr += 4; uint16_t sr = ctr.readUint16(globCsr); globCsr += 2; // only support loop within entire region bool isRepeated = (!loopStart && len == loopEnd); if (len) { ids.push_back(i); isRepeatedList.push_back(isRepeated); deltaNs.push_back(SampleADPCM::calculateADPCMDeltaN(sr)); std::vector&& smp = ctr.getSubcontainer(SAMP_OFFS + start, len).toVector(); std::vector buf(smp.size()); std::transform(smp.begin(), smp.end(), buf.begin(), [](uint8_t v) { // Centering return (static_cast(v) - std::numeric_limits::max()) << 8; }); smp.resize((smp.size() + 1) / 2); smp.shrink_to_fit(); smp.back() = 0; // Clear last data codec::ymb_encode(buf.data(), smp.data(), buf.size()); samples.push_back(std::move(smp)); } } return new PziBank(ids, deltaNs, isRepeatedList, samples); } AbstractInstrument* PziIO::loadInstrument(const std::vector& sample, int deltaN, bool isRepeated, std::weak_ptr instMan, int instNum) { std::shared_ptr instManLocked = instMan.lock(); int sampIdx = instManLocked->findFirstAssignableSampleADPCM(); if (sampIdx < 0) throw FileCorruptionError(FileType::Bank, 0); InstrumentADPCM* adpcm = new InstrumentADPCM(instNum, "", instManLocked.get()); adpcm->setSampleNumber(sampIdx); instManLocked->storeSampleADPCMRawSample(sampIdx, sample); instManLocked->setSampleADPCMRootKeyNumber(sampIdx, 67); // o5g instManLocked->setSampleADPCMRootDeltaN(sampIdx, deltaN); instManLocked->setSampleADPCMRepeatEnabled(sampIdx, isRepeated); instManLocked->setSampleADPCMRepeatrange(sampIdx, SampleRepeatRange(0, (sample.size() - 1) >> 5)); return adpcm; } } BambooTracker-0.6.5/BambooTracker/io/pzi_io.hpp000066400000000000000000000027461476276175200214130ustar00rootroot00000000000000/* * Copyright (C) 2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "bank_io.hpp" namespace io { class PziIO final : public AbstractBankIO { public: PziIO(); AbstractBank* load(const BinaryContainer& ctr) const override; static AbstractInstrument* loadInstrument(const std::vector& sample, int deltaN, bool isRepeated, std::weak_ptr instMan, int instNum); }; } BambooTracker-0.6.5/BambooTracker/io/raw_adpcm_io.cpp000066400000000000000000000037241476276175200225360ustar00rootroot00000000000000/* * Copyright (C) 2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "raw_adpcm_io.hpp" #include "file_io_error.hpp" #include "io_utils.hpp" namespace io { RawAdpcmIO::RawAdpcmIO() : AbstractInstrumentIO("spb", "ADPCM sample", true, false) {} AbstractInstrument* RawAdpcmIO::load(const BinaryContainer& ctr, const std::string& fileName, std::weak_ptr instMan, int instNum) const { std::shared_ptr instManLocked = instMan.lock(); int sampIdx = instManLocked->findFirstAssignableSampleADPCM(); if (sampIdx < 0) throw FileCorruptionError(FileType::Inst, 0); auto adpcm = new InstrumentADPCM(instNum, fileName, instManLocked.get()); adpcm->setSampleNumber(sampIdx); instManLocked->storeSampleADPCMRawSample(sampIdx, ctr.toVector()); instManLocked->setSampleADPCMRootKeyNumber(sampIdx, 60); // o5c instManLocked->setSampleADPCMRootDeltaN(sampIdx, 0x49cd); // 16000Hz return adpcm; } } BambooTracker-0.6.5/BambooTracker/io/raw_adpcm_io.hpp000066400000000000000000000026231476276175200225400ustar00rootroot00000000000000/* * Copyright (C) 2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "instrument_io.hpp" namespace io { class RawAdpcmIO final : public AbstractInstrumentIO { public: RawAdpcmIO(); AbstractInstrument* load(const BinaryContainer& ctr, const std::string& fileName, std::weak_ptr instMan, int instNum) const override; }; } BambooTracker-0.6.5/BambooTracker/io/tfi_io.cpp000066400000000000000000000063531476276175200213640ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "tfi_io.hpp" #include "file_io_error.hpp" #include "io_utils.hpp" namespace io { TfiIO::TfiIO() : AbstractInstrumentIO("tfi", "TFM Music Maker instrument", true, false) {} AbstractInstrument* TfiIO::load(const BinaryContainer& ctr, const std::string& fileName, std::weak_ptr instMan, int instNum) const { std::shared_ptr instManLocked = instMan.lock(); if (ctr.size() != 42) throw FileCorruptionError(FileType::Inst, 0); int envIdx = instManLocked->findFirstAssignableEnvelopeFM(); if (envIdx < 0) throw FileCorruptionError(FileType::Inst, 0); InstrumentFM* inst = new InstrumentFM(instNum, fileName, instManLocked.get()); inst->setEnvelopeNumber(envIdx); instManLocked->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AL, ctr.readUint8(0)); instManLocked->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::FB, ctr.readUint8(1)); size_t csr = 2; for (int op : { 0, 2, 1, 3 }) { const auto& params = FM_OP_PARAMS[op]; instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::ML), ctr.readUint8(csr++)); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::DT), convertDtFromDmpTfiVgi(ctr.readUint8(csr++))); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::TL), ctr.readUint8(csr++)); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::KS), ctr.readUint8(csr++)); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::AR), ctr.readUint8(csr++)); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::DR), ctr.readUint8(csr++)); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::SR), ctr.readUint8(csr++)); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::RR), ctr.readUint8(csr++)); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::SL), ctr.readUint8(csr++)); int ssgeg = ctr.readUint8(csr++); ssgeg = ssgeg & 8 ? ssgeg & 7 : -1; instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::SSGEG), ssgeg); } return inst; } } BambooTracker-0.6.5/BambooTracker/io/tfi_io.hpp000066400000000000000000000026101476276175200213610ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "instrument_io.hpp" namespace io { class TfiIO final : public AbstractInstrumentIO { public: TfiIO(); AbstractInstrument* load(const BinaryContainer& ctr, const std::string& fileName, std::weak_ptr instMan, int instNum) const override; }; } BambooTracker-0.6.5/BambooTracker/io/vgi_io.cpp000066400000000000000000000077251476276175200213730ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "vgi_io.hpp" #include "file_io_error.hpp" #include "io_utils.hpp" namespace io { VgiIO::VgiIO() : AbstractInstrumentIO("vgi", "VGM Music Maker instrument", true, false) {} AbstractInstrument* VgiIO::load(const BinaryContainer& ctr, const std::string& fileName, std::weak_ptr instMan, int instNum) const { std::shared_ptr instManLocked = instMan.lock(); if (ctr.size() != 43) throw FileCorruptionError(FileType::Inst, 0); int envIdx = instManLocked->findFirstAssignableEnvelopeFM(); if (envIdx < 0) throw FileCorruptionError(FileType::Inst, 0); InstrumentFM* inst = new InstrumentFM(instNum, fileName, instManLocked.get()); inst->setEnvelopeNumber(envIdx); instManLocked->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AL, ctr.readUint8(0)); instManLocked->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::FB, ctr.readUint8(1)); uint8_t pams = ctr.readUint8(2); size_t csr = 3; uint8_t am[4]; for (int op : { 0, 2, 1, 3 }) { const auto& params = FM_OP_PARAMS[op]; instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::ML), ctr.readUint8(csr++)); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::DT), convertDtFromDmpTfiVgi(ctr.readUint8(csr++))); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::TL), ctr.readUint8(csr++)); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::KS), ctr.readUint8(csr++)); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::AR), ctr.readUint8(csr++)); uint8_t drams = ctr.readUint8(csr++); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::DR), drams & 31); am[op] = drams >> 7; instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::SR), ctr.readUint8(csr++)); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::RR), ctr.readUint8(csr++)); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::SL), ctr.readUint8(csr++)); int ssgeg = ctr.readUint8(csr++); ssgeg = ssgeg & 8 ? ssgeg & 7 : -1; instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::SSGEG), ssgeg); } if (pams != 0) { int lfoIdx = instManLocked->findFirstAssignableLFOFM(); if (lfoIdx < 0) throw FileCorruptionError(FileType::Inst, 43); inst->setLFOEnabled(true); inst->setLFONumber(lfoIdx); instManLocked->setLFOFMParameter(lfoIdx, FMLFOParameter::PMS, pams & 7); instManLocked->setLFOFMParameter(lfoIdx, FMLFOParameter::AMS, pams >> 4); instManLocked->setLFOFMParameter(lfoIdx, FMLFOParameter::AM1, am[0]); instManLocked->setLFOFMParameter(lfoIdx, FMLFOParameter::AM2, am[1]); instManLocked->setLFOFMParameter(lfoIdx, FMLFOParameter::AM3, am[2]); instManLocked->setLFOFMParameter(lfoIdx, FMLFOParameter::AM4, am[3]); } return inst; } } BambooTracker-0.6.5/BambooTracker/io/vgi_io.hpp000066400000000000000000000026101476276175200213640ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "instrument_io.hpp" namespace io { class VgiIO final : public AbstractInstrumentIO { public: VgiIO(); AbstractInstrument* load(const BinaryContainer& ctr, const std::string& fileName, std::weak_ptr instMan, int instNum) const override; }; } BambooTracker-0.6.5/BambooTracker/io/wav_container.cpp000066400000000000000000000171011476276175200227430ustar00rootroot00000000000000/* * Copyright (C) 2020-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "wav_container.hpp" #include #include #include "file_io_error.hpp" namespace io { namespace { inline void assertValue(bool f, size_t pos) { if (!f) throw io::FileCorruptionError(io::FileType::WAV, pos); } enum WavOffset : size_t { RIFF_OFFS = 0, FILE_SIZE_OFFS = 4, WAVE_OFFS = 8, FMT_OFS = 12, FMT_SIZE_OFFS = 16, FORMAT_OFFS = 20, NCH_OFFS = 22, RATE_OFFS = 24, BYTE_RATE_OFFS = 28, BLOCK_SIZE_OFFS = 32, BIT_SIZE_OFFS = 34, DATA_OFFS = 36, DATA_SIZE_OFFS = 40, PREPARED_SIZE = 44 }; } WavContainer::WavContainer(uint32_t rate, uint16_t nCh, uint16_t bitSize) : nCh_(nCh), bitSize_(bitSize), rate_(rate) { // RIFF header buf_.appendString("RIFF"); uint16_t byteSize = bitSize_ / 8; buf_.appendUint32(36); buf_.appendString("WAVE"); // fmt chunk buf_.appendString("fmt "); uint32_t chunkOfs = 16; buf_.appendUint32(chunkOfs); uint16_t fmtId = 1; // Raw linear PCM buf_.appendUint16(fmtId); buf_.appendUint16(nCh_); buf_.appendUint32(rate_); uint16_t blockSize = byteSize * nCh_; uint32_t byteRate = blockSize * rate_; buf_.appendUint32(byteRate); buf_.appendUint16(blockSize); buf_.appendUint16(bitSize_); // Data chunk buf_.appendString("data"); buf_.appendUint32(0); } WavContainer::WavContainer(const BinaryContainer& bc) { buf_.resize(PREPARED_SIZE); size_t p = 0; assertValue(bc.readString(p, 4) == "RIFF", p); buf_.writeString(RIFF_OFFS, "RIFF"); p += 4; uint32_t fileSize = bc.readUint32(p) + 8; assertValue(fileSize == bc.size(), p); p += 4; assertValue(bc.readString(p, 4) == "WAVE", p); buf_.writeString(WAVE_OFFS, "WAVE"); p += 4; while (p < fileSize) { std::string id = bc.readString(p, 4); p += 4; if (id == "fmt ") { buf_.writeString(FMT_OFS, "fmt "); uint32_t fmtSize = bc.readUint32(p); buf_.writeUint32(FMT_SIZE_OFFS, 16); size_t fmtp = p + 4; p = fmtp + fmtSize; assertValue(bc.readUint16(fmtp) == 1, fmtp); // Only support linear PCM buf_.writeUint16(FORMAT_OFFS, 1); fmtp += 2; nCh_ = bc.readUint16(fmtp); buf_.writeUint16(NCH_OFFS, nCh_); fmtp += 2; rate_ = bc.readUint32(fmtp); buf_.writeUint32(RATE_OFFS, rate_); fmtp += 4; byteRate_ = bc.readUint32(fmtp); buf_.writeUint32(BYTE_RATE_OFFS, byteRate_); fmtp += 4; blockSize_ = bc.readUint16(fmtp); assertValue(byteRate_ == blockSize_ * rate_, fmtp); buf_.writeUint16(BLOCK_SIZE_OFFS, blockSize_); fmtp += 2; bitSize_ = bc.readUint16(fmtp); assertValue(bitSize_ == 16, fmtp); // Only support 16-bit assertValue(blockSize_ == nCh_ * bitSize_ / 8, fmtp); buf_.writeUint16(BIT_SIZE_OFFS, bitSize_); /* fmtp += 2; */ } else if (id == "data") { buf_.writeString(DATA_OFFS, "data"); uint32_t dataSize = bc.readUint32(p); assertValue(p + dataSize <= bc.size(), p); buf_.writeUint32(DATA_SIZE_OFFS, dataSize); p += 4; buf_.appendBinaryContainer(bc.getSubcontainer(p, dataSize)); p += dataSize; } else { p += (bc.readUint32(p) + 4); // Jump to next chunk } } buf_.writeUint32(FILE_SIZE_OFFS, buf_.size() - 8); } void WavContainer::setChannelCount(uint16_t n) { nCh_ = n; buf_.writeUint16(NCH_OFFS, nCh_); updateBlockSize(); } void WavContainer::setBitSize(uint16_t size) { bitSize_ = size; buf_.writeUint16(BIT_SIZE_OFFS, bitSize_); updateBlockSize(); } void WavContainer::setSampleRate(uint32_t rate) { rate_ = rate; buf_.writeUint16(rate, rate); updateBlockSize(); updateByteRate(); } WavContainer::size_type WavContainer::getSampleCount() const { return (buf_.size() - PREPARED_SIZE) * bitSize_ / 8 / nCh_; } void WavContainer::appendSample(const int16_t* sample, size_type nSamples) { size_t dataSize = nCh_ * nSamples * sizeof(int16_t); buf_.appendArray(reinterpret_cast(sample), dataSize); updateSizeDataAfterAppendSample(); } void WavContainer::appendSample(const std::vector& sample) { size_t dataSize = sample.size() * sizeof(int16_t); buf_.appendArray(reinterpret_cast(sample.data()), dataSize); updateSizeDataAfterAppendSample(); } void WavContainer::appendSample(const BinaryContainer& sample) { buf_.appendBinaryContainer(sample); updateSizeDataAfterAppendSample(); } BinaryContainer WavContainer::getSample() const noexcept { return buf_.getSubcontainer(PREPARED_SIZE, buf_.size() - PREPARED_SIZE); } // WavContainer* WavContainer::resample(const WavContainer* src, uint32_t rate) // { // std::unique_ptr tgt // = std::make_unique(0, rate, src->getChannelCount(), src->getBitSize()); // assert(src->getBitSize() == 16); // Only support int16_t // size_t nCh = src->getChannelCount(); // size_t tsize = src->getSampleCount() * tgt->getSampleRate() / src->getSampleRate(); // BinaryContainer tbc(tsize * 2 * nCh); // BinaryContainer sbc = src->getSample(); // double r = static_cast(src->getSampleRate()) / tgt->getSampleRate(); // for (size_t n = 0; n < tsize; ++n) { // double curnf = n * r; // int curni = static_cast(curnf); // double sub = curnf - curni; // for (size_t ch = 0; ch < nCh; ++ch) { // double a = sbc.readInt16((curni * nCh + ch) * 2); // if (sub == 0.) { // double b = sbc.readInt16(((curni + 1) * nCh + ch) * 2); // tbc.appendInt16(static_cast(std::round(a + (b - a) * sub))); // } // else { // tbc.appendInt16(static_cast(std::round(a))); // } // } // } // tgt->storeSample(tbc); // return tgt.release(); // } // WavContainer* WavContainer::mono(const WavContainer* src) // { // std::unique_ptr tgt // = std::make_unique(0, src->getSampleRate(), 1, src->getBitSize()); // assert(src->getBitSize() == 16); // Only support int16_t // BinaryContainer tbc; // BinaryContainer sbc = src->getSample(); // uint16_t nCh = src->getChannelCount(); // size_t size = src->getSampleCount(); // for (size_t i = 0; i < size; ++i) { // int32_t v = 0; // for (size_t ch = 0; ch < nCh; ++ch) { // v += sbc.readInt16((i * nCh + ch) * 2); // } // tbc.writeInt16(i, static_cast(clamp(v, -32768, 32767))); // } // tgt->storeSample(tbc); // return tgt.release(); // } void WavContainer::updateBlockSize() { blockSize_ = nCh_ * bitSize_ / 8; buf_.writeUint16(BLOCK_SIZE_OFFS, blockSize_); } void WavContainer::updateByteRate() { byteRate_ = blockSize_ * rate_; buf_.writeUint32(BYTE_RATE_OFFS, byteRate_); } void WavContainer::updateSizeDataAfterAppendSample() { buf_.writeUint32(FILE_SIZE_OFFS, buf_.size() - 8); buf_.writeUint32(DATA_SIZE_OFFS, buf_.size() - PREPARED_SIZE); } } BambooTracker-0.6.5/BambooTracker/io/wav_container.hpp000066400000000000000000000067041476276175200227570ustar00rootroot00000000000000/* * Copyright (C) 2020-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include "binary_container.hpp" namespace io { class WavContainer { public: using value_type = BinaryContainer::value_type; using size_type = BinaryContainer::size_type; using iterator = BinaryContainer::iterator; using const_iterator = BinaryContainer::const_iterator; using reverse_iterator = BinaryContainer::reverse_iterator; using const_reverse_iterator = BinaryContainer::const_reverse_iterator; explicit WavContainer(uint32_t rate = 44100, uint16_t nCh = 2, uint16_t getBitSize = 16); explicit WavContainer(const BinaryContainer& bc); inline iterator begin() noexcept { return buf_.begin(); } inline const_iterator begin() const noexcept { return buf_.begin(); } inline iterator end() noexcept { return buf_.end(); } inline const_iterator end() const noexcept { return buf_.end(); } inline const_iterator cbegin() const noexcept { return buf_.cbegin(); } inline const_iterator cend() const noexcept { return buf_.cend(); } inline reverse_iterator rbegin() noexcept { return buf_.rbegin(); } inline const_reverse_iterator rbegin() const noexcept { return buf_.rbegin(); } inline reverse_iterator rend() noexcept { return buf_.rend(); } inline const_reverse_iterator rend() const noexcept { return buf_.rend(); } inline const_reverse_iterator crbegin() const noexcept { return buf_.crbegin(); } inline const_reverse_iterator crend() const noexcept { return buf_.crend(); } inline size_type size() const { return buf_.size(); } void setChannelCount(uint16_t n); inline uint16_t getChannelCount() const noexcept { return nCh_; } void setBitSize(uint16_t size); inline uint16_t getBitSize() const noexcept { return bitSize_; } void setSampleRate(uint32_t rate); inline uint32_t getSampleRate() const noexcept { return rate_; } size_type getSampleCount() const; void appendSample(const int16_t* sample, size_type nSamples); void appendSample(const std::vector& sample); void appendSample(const BinaryContainer& sample); BinaryContainer getSample() const noexcept; // static WavContainer* resample(const WavContainer* src, uint32_t rate); // static WavContainer* mono(const WavContainer* src); private: uint16_t nCh_, bitSize_, blockSize_; uint32_t rate_, byteRate_; BinaryContainer buf_; void updateBlockSize(); void updateByteRate(); void updateSizeDataAfterAppendSample(); }; } BambooTracker-0.6.5/BambooTracker/io/wopn_io.cpp000066400000000000000000000032671476276175200215660ustar00rootroot00000000000000/* * Copyright (C) 2020-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "wopn_io.hpp" #include "format/wopn_file.h" #include "file_io_error.hpp" namespace io { WopnIO::WopnIO() : AbstractBankIO("wopn", "WOPN bank", true, false) {} AbstractBank* WopnIO::load(const BinaryContainer& ctr) const { struct WOPNDeleter { void operator()(WOPNFile *x) { WOPN_Free(x); } }; std::unique_ptr wopn; auto&& mem = ctr.toVector(); wopn.reset(WOPN_LoadBankFromMem(mem.data(), mem.size(), nullptr)); if (!wopn) throw FileCorruptionError(FileType::Bank, 0); WopnBank *bank = new WopnBank(wopn.get()); wopn.release(); return bank; } } BambooTracker-0.6.5/BambooTracker/io/wopn_io.hpp000066400000000000000000000024331476276175200215650ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "bank_io.hpp" namespace io { class WopnIO final : public AbstractBankIO { public: WopnIO(); AbstractBank* load(const BinaryContainer& ctr) const override; }; } BambooTracker-0.6.5/BambooTracker/io/y12_io.cpp000066400000000000000000000064511476276175200212140ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "y12_io.hpp" #include "file_io_error.hpp" namespace io { Y12IO::Y12IO() : AbstractInstrumentIO("y12", "Gens KMod dump", true, false) {} AbstractInstrument* Y12IO::load(const BinaryContainer& ctr, const std::string& fileName, std::weak_ptr instMan, int instNum) const { std::shared_ptr instManLocked = instMan.lock(); if (ctr.size() != 128) throw FileCorruptionError(FileType::Inst, 0); int envIdx = instManLocked->findFirstAssignableEnvelopeFM(); if (envIdx < 0) throw FileCorruptionError(FileType::Inst, 0); InstrumentFM* inst = new InstrumentFM(instNum, fileName, instManLocked.get()); inst->setEnvelopeNumber(envIdx); size_t csr = 0; for (const int op : { 0, 2, 1, 3 }) { const auto& params = FM_OP_PARAMS[op]; uint8_t tmp = ctr.readUint8(csr++); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::ML), 0x0f & tmp); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::DT), 0x07 & (tmp >> 4)); tmp = ctr.readUint8(csr++); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::TL), 0x7f & tmp); tmp = ctr.readUint8(csr++); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::AR), 0x1f & tmp); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::KS), tmp >> 6); tmp = ctr.readUint8(csr++); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::DR), 0x1f & tmp); tmp = ctr.readUint8(csr++); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::SR), 0x1f & tmp); tmp = ctr.readUint8(csr++); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::RR), 0x0f & tmp); instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::SL), tmp >> 4); int ssgeg = ctr.readUint8(csr++); ssgeg = ssgeg & 8 ? ssgeg & 7 : -1; instManLocked->setEnvelopeFMParameter(envIdx, params.at(FMOperatorParameter::SSGEG), ssgeg); csr += 9; } instManLocked->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AL, ctr.readUint8(0x40)); instManLocked->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::FB, ctr.readUint8(0x41)); return inst; } } BambooTracker-0.6.5/BambooTracker/io/y12_io.hpp000066400000000000000000000026201476276175200212130ustar00rootroot00000000000000/* * Copyright (C) 2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include "instrument_io.hpp" namespace io { class Y12IO final : public AbstractInstrumentIO { public: Y12IO(); AbstractInstrument* load(const BinaryContainer& ctr, const std::string& fileName, std::weak_ptr instMan, int instNum) const override; }; } BambooTracker-0.6.5/BambooTracker/jamming.cpp000066400000000000000000000133201476276175200211160ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "jamming.hpp" #include #include #include #include #include "note.hpp" #include "utils.hpp" namespace jam_utils { Note makeNote(const JamKeyInfo& info, int baseOctave) { return (info.key == JamKey::MidiKey) ? Note(info.keyNum) : makeNote(baseOctave, info.key); } Note makeNote(int baseOctave, JamKey key) { switch (key) { case JamKey::LowC: return Note(baseOctave, Note::C); case JamKey::LowCS: return Note(baseOctave, Note::CS); case JamKey::LowD: return Note(baseOctave, Note::D); case JamKey::LowDS: return Note(baseOctave, Note::DS); case JamKey::LowE: return Note(baseOctave, Note::E); case JamKey::LowF: return Note(baseOctave, Note::F); case JamKey::LowFS: return Note(baseOctave, Note::FS); case JamKey::LowG: return Note(baseOctave, Note::G); case JamKey::LowGS: return Note(baseOctave, Note::GS); case JamKey::LowA: return Note(baseOctave, Note::A); case JamKey::LowAS: return Note(baseOctave, Note::AS); case JamKey::LowB: return Note(baseOctave, Note::B); case JamKey::LowC2: case JamKey::HighC: return Note(baseOctave + 1, Note::C); case JamKey::LowCS2: case JamKey::HighCS: return Note(baseOctave + 1, Note::CS); case JamKey::LowD2: case JamKey::HighD: return Note(baseOctave + 1, Note::D); case JamKey::HighDS: return Note(baseOctave + 1, Note::DS); case JamKey::HighE: return Note(baseOctave + 1, Note::E); case JamKey::HighF: return Note(baseOctave + 1, Note::F); case JamKey::HighFS: return Note(baseOctave + 1, Note::FS); case JamKey::HighG: return Note(baseOctave + 1, Note::G); case JamKey::HighGS: return Note(baseOctave + 1, Note::GS); case JamKey::HighA: return Note(baseOctave + 1, Note::A); case JamKey::HighAS: return Note(baseOctave + 1, Note::AS); case JamKey::HighB: return Note(baseOctave + 1, Note::B); case JamKey::HighC2: return Note(baseOctave + 2, Note::C); case JamKey::HighCS2: return Note(baseOctave + 2, Note::CS); case JamKey::HighD2: return Note(baseOctave + 2, Note::D); default: throw std::invalid_argument("invalid jam key"); } } } JamManager::JamManager() : isJamMode_(true), isPoly_(true) { reset(); } std::vector JamManager::keyOn(JamKey key, int channel, SoundSource source, int keyNum) { std::vector keyDataList; JamKeyInfo onData{ key, channel, source, keyNum }; std::deque& unusedCh = unusedCh_.at(source); if (!unusedCh.empty()) { if (key == JamKey::MidiKey) { if (isPoly_) onData.channelInSource = unusedCh.front(); unusedCh.pop_front(); keyOnTable_.push_back(onData); keyDataList.push_back(onData); } else { auto&& it = utils::findIf(keyOnTable_, [&](JamKeyInfo x) { return (x.source == source && x.key == key); }); if (it == keyOnTable_.end()) { if (isPoly_) onData.channelInSource = unusedCh.front(); unusedCh.pop_front(); keyOnTable_.push_back(onData); keyDataList.push_back(onData); } else { JamKeyInfo del = *it; if (isPoly_) onData.channelInSource = del.channelInSource; keyDataList.push_back(onData); keyDataList.push_back(del); keyOnTable_.erase(it); keyOnTable_.push_back(onData); } } } else { auto&& it = utils::findIf(keyOnTable_, [&](JamKeyInfo x) { return (x.source == source); }); JamKeyInfo del = *it; if (isPoly_) onData.channelInSource = del.channelInSource; keyDataList.push_back(onData); keyDataList.push_back(del); keyOnTable_.erase(it); keyOnTable_.push_back(onData); } return keyDataList; } JamKeyInfo JamManager::keyOff(JamKey key, int keyNum) { JamKeyInfo keyData; auto cond = (key == JamKey::MidiKey) ? std::function([&](JamKeyInfo x) -> bool { return (x.key == JamKey::MidiKey && x.keyNum == keyNum); }) : std::function([&](JamKeyInfo x) -> bool { return (x.key == key); }); auto&& it = utils::findIf(keyOnTable_, cond); if (it == keyOnTable_.end()) { keyData.channelInSource = -1; // Already released } else { JamKeyInfo data = *it; keyData.channelInSource = data.channelInSource; keyData.key = key; keyData.source = data.source; unusedCh_.at(keyData.source).push_front(keyData.channelInSource); keyOnTable_.erase(it); } return keyData; } std::vector JamManager::reset() { if (isPoly_) { unusedCh_[SoundSource::FM] = std::deque(6); unusedCh_[SoundSource::SSG] = std::deque(3); unusedCh_[SoundSource::ADPCM] = std::deque(1); for (auto& pair : unusedCh_) std::iota(pair.second.begin(), pair.second.end(), 0); } else { for (auto& pair : unusedCh_) pair.second.resize(1); } auto on = keyOnTable_; keyOnTable_.clear(); return on; } BambooTracker-0.6.5/BambooTracker/jamming.hpp000066400000000000000000000045651476276175200211360ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include "enum_hash.hpp" #include "bamboo_tracker_defs.hpp" class Note; enum class JamKey { LowC, LowCS, LowD, LowDS, LowE, LowF, LowFS, LowG, LowGS, LowA, LowAS, LowB, LowC2, LowCS2, LowD2, HighC, HighCS, HighD, HighDS, HighE, HighF, HighFS, HighG, HighGS, HighA, HighAS, HighB, HighC2, HighCS2, HighD2, MidiKey }; struct JamKeyInfo { JamKey key; int channelInSource; SoundSource source; int keyNum; }; namespace jam_utils { Note makeNote(const JamKeyInfo& info, int baseOctave); Note makeNote(int baseOctave, JamKey key); } class JamManager { public: JamManager(); bool toggleJamMode(); bool isJamMode() const noexcept { return isJamMode_; } void polyphonic(bool flag); std::vector keyOn(JamKey key, int channel, SoundSource source, int keyNum); JamKeyInfo keyOff(JamKey key, int keyNum); std::vector reset(); private: bool isJamMode_; bool isPoly_; std::vector keyOnTable_; std::unordered_map> unusedCh_; }; //=============================================== inline bool JamManager::toggleJamMode() { isJamMode_ = !isJamMode_; return isJamMode_; } inline void JamManager::polyphonic(bool flag) { isPoly_ = flag; reset(); } BambooTracker-0.6.5/BambooTracker/lang/000077500000000000000000000000001476276175200177125ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/lang/CMakeLists.txt000066400000000000000000000006151476276175200224540ustar00rootroot00000000000000set (BT_TRANSLATIONS bamboo_tracker_ja.ts bamboo_tracker_fr.ts bamboo_tracker_pl.ts bamboo_tracker_es.ts ) if (QT_VERSION EQUAL 6) qt6_add_translation (TRANSLATIONS ${BT_TRANSLATIONS}) else() qt5_add_translation (TRANSLATIONS ${BT_TRANSLATIONS}) endif() add_custom_target (BambooTracker_translations ALL DEPENDS ${TRANSLATIONS}) install (FILES ${TRANSLATIONS} DESTINATION "${BT_LANGDIR}") BambooTracker-0.6.5/BambooTracker/lang/bamboo_tracker_es.ts000066400000000000000000005665041476276175200237430ustar00rootroot00000000000000 ADPCMSampleEditor Users: Usuarios: Memory: Memoria: Repeat Repetir Root key Tonalidad fundamental Key Tonalidad Rate Frecuencia de muestreo &Resize Cambiar tamaño(&R) Re&verse Re&vertir Zoom I&n Acercar(&N) Zoom &Out Alejar(&O) &Import &Importar &Clear Borrar(&C) &Grid View Vista en cuadrícula(&G) Grid &Settings... Configuración de cuadrícula(&S)... &Draw Sample &Dibujar muestra Direc&t Draw Dibujar direc&tamente Supported sample rate is 2kHz-55.5kHz, but the rate of selected sample is %1. La frecuencia de muestreo admitida es de 2kHz-55.5kHz, pero la frecuencia de la muestra seleccionada es %1. The selected sample is not mono channel. La muestra seleccionada no es mono. Import sample Importar muestra WAV signed 16-bit PCM (*.wav) WAV con signo 16-bit PCM (*.wav) All files (*) Todos los archivos (*) AdpcmDrumkitEditor Sample assignment Asignación de muestra Key Tonalidad Pitch Tono Pan Panear Panning Paneo Sample Muestra AdpcmInstrumentEditor Sample Muestra Envelope Envolvente Users: Usuarios: Arpeggio Arpegio Type: Tipo: Pitch Tono Panning Paneo Absolute Absoluta Fixed Fija Relative Relativa BookmarkManagerForm Bookmark Manager Administrador de marcadores Sort by Ordenar por Position Posición Name Nombre Remove Eliminar Clear All Borrar todo Bookmark editor Editor de marcadores Order Orden Step Paso Create New Crear Nuevo Update Actualizar Bookmark Marcador Bookmark %1 Marcador %1 CommentEditDialog Module Comment Comentario del módulo ConfigurationDialog Configuration Configuración General General Wave view Vista de onda Frame rate Cuadros por segundo Edit settings Editar ajustes Page jump length Longitud de salto de página General settings Configuración general Wrap cursor Envolver el cursor Wrap across orders Envolver órdenes Show row numbers in hex Mostrar números de fila en hexadecimal Preview previous/next orders Previsualizar órdenes anteriores/siguientes Backup modules Copia de seguridad de módulos Don't select on double click No seleccionar al hacer doble click Reverse FM volume order Revertir orden de volúmen de FM Move cursor right Mover el cursor a la derecha Retrieve channel state Recuperar el estado del canal Enable translation Habilitar traducción Show FM detune as signed Mostrar valores de fluctuación de FM con signo Fill 00 to effect value Establecer automáticamente 00 como valor de efecto Move cursor with horizontal scroll bar Mover el cursor con la barra de desplazamiento horizontal Overwrite unused and unedited properties Sobrescribir propiedades no utilizadas y no editadas Write only used samples Escribir sólo muestras utilizadas Reflect instrument number change Reflejar cambio de número de instrumento Fix jamming volume Volúmen fijo durante jam Mute hidden tracks Silenciar pistas ocultas Restore track visibility Restaurar visibilidad de pistas Description: Descripción: Note names Nombres de notas Notation system Sistema de notación Sound Sonido Sample rate Frecuencia de muestreo Resampler Remuestreador Buffer length Longitud del búfer 1ms 1ms MIDI input Entrada MIDI Device Dispositivo API API Audio output Salida de audio Real chip interface Interfaz de chip real Emulation core Emulador de chips Zero-wait write No esperar para escritura de registro Mixer Mezclador Part Parte The level setting for each part is valid when the mixer in the module properties is not checked. La configuración de nivel para cada parte es válida cuando el mezclador en las propiedades del módulo no está marcado. Reset Reiniciar Appearance Apariencia Font and size Fuente y tamaño Pattern editor header Encabezado del editor de patrones Encabezado del editor de patrones Pattern editor rows Filas del editor de patrones Filas del editor de patrones Order list header Encabezado de la lista de órdenes Order list rows Filas de la lista de órdenes Colors Colores Edit Editar Item Elemento Color Color Pattern editor Editor de patrones Default step text Texto de paso predeterminado Default step background Fondo de paso predeterminado Highlighted step 1 background Fondo resaltado de paso 1 Highlighted step 2 background Fondo resaltado de paso 2 Current step text Texto de paso actual Current step background Fondo de paso actual Current editing step background Fondo de paso siendo editado actualmente Current cell background Fondo de celda actual Current playing step background Fondo de paso siendo reproducido actualmente Selection background Fondo de selección Hovered cell background Fondo de celda donde está el cursor Default step number Número de paso predeterminado Highlighted step 1 number Número resaltado de paso 1 Highlighted step 2 number Número resaltado de paso 2 Note text Texto de nota Instrument text Texto de instrumento Volume text Texto de volúmen Effect text Texto de efecto Error text Texto de error Header text Texto de encabezado Header background Fondo de encabezado Mask Máscara Border Borde Header border Borde de encabezado Mute Silenciar Unmute No silenciar Background Fondo Marker Marcador Unfocused shadow Sombra desenfocada Order list Lista de órdenes Default row text Texto de fila predeterminado Default row background Fondo de fila predeterminado Current row text Texto de fila actual Current row background Fondo de fila actual Current editing row background Fondo de fila siendo editada actualmente Current playing row background Fondo de fila siendo reproducida actualmente Row number Número de fila Instrument list Lista de instrumentos Default text Texto predeterminado Selected background Fondo seleccionado Hovered background Fondo donde está el cursor Selected hovered background Fondo donde está el cursor seleccionado Oscilloscope Osciloscopio Foreground Primer plano Save Guardar Load Cargar Formats Formatos FM envelope text Texto de envolvente FM Add Añadir Remove Eliminar Keys Teclas Shortcuts Atajos Action Acción Note entry layout Diseño de entrada de notas Custom Personalizado Low Bajo High Alto Wrap the cursor around the edges of the pattern editor. Envolver el cursor alrededor de los bordes del editor de patrones. Move to the previous or next order when reaching the top or bottom in the pattern editor. Mover a la orden anterior o siguiente al alcanzar la parte superior o la parte inferior en el editor de patrones. Display row numbers and the playback position on the status bar in hexadecimal. Mostrar números de fila y posición de reproducción en la barra de estado en hexadecimal. Preview the previous and next orders in the pattern editor. Previsualizar la orden anterior o siguiente en el editor de patrones. Create a backup copy of the existing file when saving a module. Crear una copia de seguridad del archivo existente al guardar un módulo. Don't select the whole track when double-clicking in the pattern editor. No seleccionar la pista entera al hacer doble click en el editor de patrones. Reverse the order of FM volume so that 00 is the quietest in the pattern editor. Revertir la orden del volúmen de FM para que 00 sea lo más silencioso en el editor de patrones. Automatically move the cursor right after entering effects in the pattern editor. Automaticamente mover el cursor hacia la derecha luego de introducir efectos en el editor de patrones. Reconstruct the current channel's state from previous orders upon playing. Reconstruir el estado del canal actual a partir de órdenes anteriores al reproducir. Translate to your language from the next launch. See readme to check supported languages. Traducir a su idioma la próxima vez que abra el programa. Ver readme para chequear idiomas compatibles. Display FM detune values as signed numbers in the FM envelope editor. Mostrar valores de fluctuación de FM como números con signo en el editor de envolvente FM. Automatically fill 00 to the effect value column upon entering an effect ID. Llenar automáticamente con 00 la columna de valores de efecto al ingresar un ID de efecto. Move the cursor with the horizontal scroll bar in the order list and the pattern editor. Mover el cursor con la barra de desplazamiento horizontal en la lista de órdenes y en el editor de patrones. Overwrite unused and unedited instrument properties when creating new properties. If disabled, override unused properties regardless of editing. Sobrescribir propiedades de instrumento no utilizadas y no editadas al crear nuevas propiedades. Only send samples used by instruments to the ADPCM memory. It is recommended to turn this off if you change ADPCM samples frequently due to the high cost of rewriting.. Enviar solo muestras utilizadas por instrumentos a la memoria ADPCM. No es recomendable si cambia muestras ADPCM frecuentemente debido al alto costo de reescritura. Automatically update the instrument number in patterns when an instrument's number is changed. Actualizar automáticamente el número de instrumento en patrones al cambiar el número de instrumento. Set maximum volume during jam mode. When unchecked, the volume is changed by the volume spinbox. Establecer volúmen máximo durante modo Jam. Cuando no está marcado, el volúmen se cambia desde el spinbox de volúmen. Automatically mute tracks when they are hidden. Automáticamente silenciar pistas cuando están ocultas. Restore the previous track visibility on startup. Restaurar la visibilidad de pistas previa al iniciar el programa. Key release Soltar tecla Key cut Cortar tecla Octave up Octava arriba Octave down Octava abajo Echo buffer Búfer de eco Play and stop Reproducir y detener Play Reproducir Play from start Reproducir desde el principio Play pattern Reproducir patrón Play from cursor Reproducir desde cursor Play from marker Reproducir desde marcador Play step Reproducir paso Stop Detener Focus on pattern editor Enfocar en editor de patrones Focus on order list Enfocar en lista de órdenes Focus on instrument list Enfocar en lista de instrumentos Toggle edit/jam mode Cambio de modo edit/jam Set marker Establecer marcador Paste and mix Pegar y mezclar Paste and overwrite Pegar y sobrescribir Paste and insert Pegar e insertar Select all Seleccionar todo Deselect Deseleccionar Select row Seleccionar fila Select column Seleccionar columna Select pattern Seleccionar patrón Select order Seleccionar orden Go to step Ir al paso Toggle track Silenciar pista Solo track Cambiar pista a estado solo Interpolate Interpolar Reverse Revertir Revertir Go to previous order Ir a la orden anterior Go to next order Ir a la próxima orden Toggle bookmark Configuración del marcador Previous bookmark Marcador anterior Next bookmark Próximo marcador Transpose down one semitone Transponer un semitono hacia abajo Transpose up one semitone Transponer un semitono hacia arriba Transpose down one octave Transponer una octava hacia abajo Transpose up one octave Transponer una octava hacia arriba Previous instrument Instrumento anterior Next instrument Próximo instrumento Mask instrument Enmascarar instrumento Mask volume Enmascarar volúmen Edit instrument Editar instrumento Follow mode Modo de seguimiento Duplicate order Duplicar orden Clone patterns Clonar patrones Clone order Clonar orden Replace instrument Reemplazar instrumento Expand pattern Expandir patrón Shrink pattern Encoger patrón Fine decrease values Disminuir el valor en 1 Fine increase values Incrementar el valor en 1 Coarse decrease values Disminuir el valor en 16 Coarse increase valuse Incrementar el valor en 16 Expand effect column Expandir columna de efectos Shrink effect column Encoger columna de efectos Previous highlighted step Paso resaltado anterior Next highlighted step Próximo paso resaltado Increase pattern size Aumentar tamaño de patrón Decrease pattern size Disminuir tamaño de patrón Increase edit step Aumentar el paso de edición Decrease edit step Disminuir el paso de edición Display effect list Mostrar lista de efectos Previous song Canción anterior Next song Próxima canción Jam volume up Subir el volúmen al improvisar Jam volume down Bajar el volúmen al improvisar None Ninguno old, deprecated viejo, obsoleto fast rápido Master Master The change of emulator will be effective after restarting the program. El cambio de emulador se hará efectivo tras reiniciar el programa. Description: %1 Descripción: %1 Virtual port Puerto virtual Set %1 Establecer %1 Open color scheme Abrir esquema de colores ini file (*.ini) archivo ini (*.ini) All files (*) Todos los archivos (*) Error Error An unknown error occurred while loading the color scheme. Ocurrió un error desconocido al cargar el esquema de colores. Save color scheme Guardar esquema de colores Failed to save the color scheme. Falló al guardar el esquema de colores. EffectDescription Arpeggio, x: 2nd note (0-F), y: 3rd note (0-F) Arpegio, x: 2da nota (0-F), y: 3ra nota (0-F) Portamento up, xx: depth (00-FF) Portamento arriba, xx: profundidad (00-FF) Portamento down, xx: depth (00-FF) Portamento abajo, xx: profundidad (00-FF) Tone portamento, xx: depth (00-FF) Portamento de tono, xx: profundidad (00-FF) Vibrato, x: period (0-F), y: depth (0-F) Vibrato, x: período (0-F), y: profundidad (0-F) Tremolo, x: period (0-F), y: depth (0-F) Tremolo, x: período (0-F), y: profundidad (0-F) Pan, xx: 00 = no sound, 01 = right, 02 = left, 03 = center Panear, xx: 00 = no sonido, 01 = derecha, 02 = izquierda, 03 = centro Volume slide, x: up (0-F), y: down (0-F) Deslizamiento de volúmen, x: arriba (0-F), y: abajo (0-F) Jump to beginning of order xx Saltar al comienzo de la orden xx End of song Fin de la canción Jump to step xx of next order Saltar al paso xx de la próxima orden Change speed (xx: 00-1F), change tempo (xx: 20-FF) Cambiar velocidad (xx: 00-1F), cambiar tempo (xx: 20-FF) Note delay, xx: count (00-FF) Delay de nota, xx: contador (00-FF) Auto envelope, x: shift amount (0-F), y: shape (0-F) Envolvente automática, x: desplazamiento (0-F), y: forma (0-F) Hardware envelope period 1, xx: high byte (00-FF) Período de envolvente de hardware 1, xx: byte alto (00-FF) Hardware envelope period 2, xx: low byte (00-FF) Período de envolvente de hardware 2, xx: byte bajo (00-FF) Retrigger, x: volume slide (0-7: up, 8-F: down), y: tick (1-F) Retrigger, x: deslizar volúmen (0-7: arriba, 8-F: abajo), y: tick (1-F) Set groove xx Establecer groove xx Detune, xx: pitch (00-FF) Desafinar, xx: tono (00-FF) Note slide up, x: count (0-F), y: semitone (0-F) Deslizar nota hacia arriba, x: contador (0-F), y: semitono (0-F) Note slide down, x: count (0-F), y: semitone (0-F) Deslizar nota hacia abajo, x: contador (0-F), y: semitono (0-F) Note release, xx: count (00-FF) Soltar nota, xx: contador (00-FF) Transpose delay, x: count (0-7: up, 8-F: down), y: semitone (0-F) Transponer delay, x: contador (0-7: arriba, 8-F: abajo), y: semitono (0-F) Tone/Noise mix, xx: 00 = no sound, 01 = tone, 02 = noise, 03 = tone & noise Mix Tono/Ruido, xx: 00 = no sonido, 01 = tono, 02 = ruido, 03 = tono & ruido Master volume, xx: volume (00-3F) Volúmen Master, xx: volúmen (00-3F) Noise pitch, xx: pitch (00-1F) Tono del ruido, xx: tono (00-1F) Register address bank 0, xx: address (00-6B) Registrar dirección del banco de sonido 0, xx: dirección (00-6B) Register address bank 1, xx: address (00-6B) Registrar dirección del banco de sonido 1, xx: dirección (00-6B) Register value set, xx: value (00-FF) Registrar valor, xx: valor (00-FF) AR control, x: operator (1-4), yy: attack rate (00-1F) Control AR, x: operador (1-4), yy: valor AR (00-1F) Brightness, xx: relative value (01-FF) Brillo, xx: valor relativo (01-FF) DR control, x: operator (1-4), yy: decay rate (00-1F) Control DR, x: operador (1-4), yy: valor DR (00-1F) Extended volume slide, x: up (0-F), y: down (0-F) Slide de volúmen extendido, x: arriba (0-F), y: abajo (0-F) Note cut, xx: count (00-FF) Corte de nota, xx: contador (00-FF) FB control, xx: feedback value (00-07) Control FB xx: valor FB (00-07) Fine detune, xx: pitch (00-FF) Desafinación fina, xx: tono (00-FF) ML control, x: operator (1-4), y: multiple (0-F) Control ML, x: operador (1-4), y: múltiplo (0-F) Volume delay, x: count (0-F), yy: volume (00-FF) Delay de volúmen, x: contador (0-F), yy: volúmen (00-FF) RR control, x: operator (1-4), y: release rate (0-F) Control RR, x: operador (1-4), y: valor RR (0-F) TL control, x: operator (1-4), yy: total level (00-7F) Control TL, x: operador (1-4), yy: valor TL (00-7F) Invalid effect Efecto inválido EffectListDialog Effect list Lista de efectos Effect Efecto FM FM SSG SSG Rhythm Ritmo ADPCM ADPCM Description Descripción FMEnvelopeSetEditDialog Edit FM envelope set Editar conjunto de envolventes FM Add Añadir Set digit types in the order of appearance. Establecer tipos de dígitos en orden de apariencia. Remove Eliminar Number Número Type Tipo Skip Omitir FMOperatorTable SSGEG SSGEG Type Tipo Operator %1 Operador %1 Copy envelope Copiar envolvente Paste envelope Pegar envolvente Paste envelope From Pegar envolvente desde Copy operator Copiar operador Paste operator Pegar operador FileIOErrorMessageBox Path does not exist. La ruta no existe. Unsupported file format. Formato de archivo incompatible. Could not load the %1 properly. Please make sure that you have the latest version of BambooTracker. No se pudo cargar el %1 correctamente. Por favor asegurese de tener la última versión de Bamboo Tracker. Could not load the %1. It may be corrupted. Stopped at %2. No se pudo cargar el %1. Puede estar corrupto. Se detuvo en %2. Failed to load %1. Falló al cargar %1. Failed to export to %1. Falló al exportar a %1. Failed to save the %1. Falló al guardar %1. Error Error Could not open the file. No se pudo abrir el archivo. FileType module módulo s98 s98 vgm vgm wav wav bank banco de sonido instrument instrumento FmInstrumentEditor Envelope Envolvente Users: Usuarios: LFO/Operator sequence LFO/Secuencia de operadores Operator sequence Secuencia de operadores Operator: Operador: Sequence Secuencia LFO LFO Start count: Empezar cuenta: AM operators Operadores AM Operator 1 Operador 1 Operator 2 Operador 2 Operator 3 Operador 3 Operator 4 Operador 4 Envelope reset Reiniciar envolvente Reset envelope before key on Reiniciar envolvente antes de apretar tecla FM 3ch FM 3ch Arpeggio/Pitch Arpegio/Tono Arpeggio Arpegio Type: Tipo: Pitch Tono Panning Paneo Freq Frecuencia All Todo Absolute Absoluta Fixed Fija Relative Relativa Error Error Did not match the clipboard text format with %1. No coincidió el formato del texto del portapapeles con %1. Copy envelope Copiar envolvente Paste envelope Pegar envolvente Paste envelope From Pegar envolvente desde Copy LFO parameters Copiar parámetros de LFO Paste LFO parameters Pegar parámetros de LFO FontInfoWidget Form Forma Custom Personalizado Bold Negrita Italic Cursiva GoToDialog Go To Ir a Order Orden Step Paso Track Pista GridSettingsDialog Grid Settings Configuración de cuadrícula Interval Intervalo GrooveSettingsDialog Groove Settings Configuración de groove Remove Eliminar Add Añadir Sequence Secuencia Copy Fxx Copiar Fxx Expand Expandir Generate Generar Shrink Encoger Pad Pad Speed: %1 Velocidad: %1 HideTracksDialog Hide Tracks Ocultar pistas Check All Marcar todo Reverse Revertir Set the visibility of tracks: Establecer la visibilidad de las pistas: InstrumentSelectionDialog Select instruments Seleccionar instrumentos Search... Buscar... KeySignatureManagerForm Key Signature Manager Administrador de armaduras de clave Key signature editor Editor de armaduras de clave Key Tonalidad Order Orden Step Paso Create New Crear Nuevo Update Actualizar Remove Eliminar Clear All Borrar todo KeyboardShortcutListDialog Keyboard shortcuts Atajos del teclado qrc:/doc/shortcuts qrc:/doc/shortcuts MainWindow BambooTracker BambooTracker Order List Lista de órdenes Song Settings Configuración de canción Tempo Tempo Speed Velocidad Pattern size Tamaño de patrón Groove Groove Edit settings Editar configuración Step Paso Key repetition Repetición de tecla Module Settings Configuración del módulo Title Título Author Autor Copyright Derechos de autor Songs Canciones Instruments Instrumentos &File Archivo(&F) &Export &Exportar &Recent Files Archivos &Recientes &Edit &Editar &Select &Seleccionar Paste Specia&l Pegar Especial(&I) &Bookmarks Marcadores(&B) Patter&n Patró&n &Transpose &Transponer &Change Values &Cambiar valores S&ong Canción(&O) &Module &Módulo Clean&up Limpiar(&U) &Instrument &Instrumento &Tracker &Tracker &Help Ayuda(&H) Vie&w Vista(&W) Main toolbar Barra de herramientas principal Secondary toolbar Barra de herramientas secundaria &New... &Nuevo... Ctrl+N Ctrl+N &Open... Abrir(&O)... Ctrl+O Ctrl+O &Save Guardar(&S) Ctrl+S Ctrl+S Save &As... Guardar como(&A)... E&xit Salir(&X) &Undo Deshacer(&U) Ctrl+Z Ctrl+Z &Redo &Rehacer Ctrl+Y Ctrl+Y Cu&t Cor&tar Ctrl+X Ctrl+X &Copy &Copiar Ctrl+C Ctrl+C &Paste &Pegar Ctrl+V Ctrl+V &Delete Eliminar(&D) Del Supr &All Todo(&A) Ctrl+A Ctrl+A &None &Ninguno Esc E&xpand E&xpandir S&hrink Encoger(&H) &Decrease Note &Disminuir nota Ctrl+F1 Ctrl+F1 &Increase Note &Incrementar nota Ctrl+F2 Ctrl+F2 D&ecrease Octave Disminuir octava(&E) Ctrl+F3 Ctrl+F3 I&ncrease Octave I&ncrementar octava Ctrl+F4 Ctrl+F4 &Insert Order &Insertar orden &Remove Order Eliminar orden(&R) &Module Properties... Propiedades del &Módulo... Ctrl+P Ctrl+P &New Instrument &Nuevo instrumento &Remove Instrument Eliminar instrumento(&R) &Clone Instrument &Clonar instrumento &Deep Clone Instrument Clonación profunda de instrumento(&D) &Load From File... Cargar desde archivo(&L)... &Save To File... Guardar en archivo(&S)... &Edit... &Editar... Ctrl+I Ctrl+I &Play Reproducir(&P) Play P&attern Reproducir p&atrón F6 F6 Play &From Start Reproducir desde el principio(&F) F5 F5 Play From C&ursor Reproducir desde c&ursor F7 F7 &Stop Detener(&S) F8 F8 &Edit Mode Modo &Edición Space Espacio To&ggle Track Reproducir pista(&G) Alt+F9 Alt+F9 S&olo Track Pista s&olista Alt+F10 Alt+F10 &Kill Sound Silenciar sonido(&K) F12 F12 &About... &Acerca de... Fo&llow Mode Modo de seguimiento(&L) ScrollLock BloqScroll &Groove Settings... Configuración de &Groove... &Configuration... &Configuración... &Duplicate Order &Duplicar orden Ctrl+D Ctrl+D Move Order &Up Mover orden hacia arriba(&U) Move Order Do&wn Mover orden hacia abajo(&W) &Clone Patterns &Clonar patrones Alt+D Alt+D Clone &Order Clonar &Orden &Comments... &Comentarios... &Interpolate &Interpolar Ctrl+G Ctrl+G &Reverse &Revertir Ctrl+R Ctrl+R R&eplace Instrument R&eemplazar instrumento Alt+S Alt+S &Row Fila(&R) &Column &Columna &Pattern &Patrón &Order &Orden Remove Unused &Instruments Eliminar &Instrumentos no utilizados Remove Unused &Patterns Eliminar &Patrones no utilizados &WAV... &WAV... &VGM... &VGM... &Mix &Mezclar Ctrl+M Ctrl+M &Overwrite Sobrescribir(&O) &Import From Bank File... &Importar desde banco de sonido... &S98... &S98... &Clear Borrar(&C) &Effect List... Lista de &Efectos... Effect List Lista de efectos F1 F1 &Shortcuts... Atajos(&S)... E&xport To Bank File... E&xportar a banco de sonido... Remove &Duplicate Instruments Eliminar Instrumentos &Duplicados Re&name Instrument Re&nombrar instrumento &Bookmark Manager... Administrador de marcadores(&B)... Fine &Decrease Values &Disminuir el valor en 1 Shift+F1 Shift+F1 Fine &Increase Values &Incrementar el valor en 1 Shift+F2 Shift+F2 Coarse D&ecrease Values Disminuir el valor en 16(&E) Shift+F3 Shift+F3 Coarse I&ncrease Values I&ncrementar el valor en 16 Shift+F4 Shift+F4 &Toggle Bookmark Configuración del marcador(&T) Ctrl+K Ctrl+K &Next Bookmark Próximo marcador(&N) Ctrl+PgDown Ctrl+AvPág &Previous Bookmark Marcador anterior(&P) Ctrl+PgUp Ctrl+RePág &Instrument Mask Máscara de &Instrumento &Volume Mask Máscara de &Volúmen Set Ro&w Marker Establecer marcador de fila(&W) Ctrl+B Ctrl+B Play From &Marker Reproducir desde &Marcador Ctrl+F7 Ctrl+F7 &Go To... Ir a(&G)... Alt+G Alt+G Remove Unused &ADPCM Samples Eliminar samples &ADPCM no utilizados &Status Bar Barra de estado(&S) &Toolbar Barra de herramientas(&T) New Drumki&t Nuevo ki&t de batería &Wave View Vista de onda(&W) &Transpose Song... &Transponer canción... &Swap Tracks... Intercambiar pistas(&S)... &Insert &Insertar &Cursor &Cursor &Selection &Selección &Fill Llenar(&F) &Hide Tracks... Ocultar pistas(&H)... &Estimate Song Length... &Estimar duración de canción... &Key Signature Manager... Administrador de armaduras de clave(&K)... &Welcome... Bienvenido(&W)... English Default notation system Set the name of suitable notation system (English or German) Inglés Octave Octava Octave: %1 Octava: %1 Volume Volúmen Step highlight 1st Resaltar paso 1ero 2nd 2do Welcome to BambooTracker v%1! Bienvenido a BambooTracker v%1! Welcome to BambooTracker! Bienvenido a BambooTracker! Don't know where to start? ¿No sabe por dónde empezar? Check the demo modules and instruments included with your download of BambooTracker. Chequee los módulos e instrumentos de demo incluidos con su descarga de BambooTracker. Need a list of effects and shortcuts? ¿Necesita una lista de efectos y atajos? Check the Help menu at the top of the window. Chequee el menú de Ayuda en la parte superior de la ventana. Still lost? ¿Sigue perdido? The README.md has a link to our Discord server. README.md tiene un link a nuestro servidor de Discord. Think you've found a bug? Missing a feature? ¿Cree que ha encontrado un error? ¿Falta una funcionalidad? BambooTracker is still in development, bugs and missing features are to be expected. So we need your help! BambooTracker aún está en desarrollo, los errores y funcionalidades faltantes son de esperar. ¡Así que necesitamos su ayuda! Please report any bugs you find and requests and features you'd like to see on our Discord server or our bug tracker (%1). Por favor reporte cualquier error que encuentre y peticiones y funcionalidades que quiera ver en nuestro servidor de Discord o en nuestro rastreador de errores (%1). If you're a developer yourself or would like to start being one, consider contributing to the project yourself. Any help would be appreciated! Si usted es un desarrollador o quiere empezar a ser uno, considere contribuir al proyecto. ¡Cualquier ayuda es apreciada! Welcome Bienvenido Error Error An error occurred in the audio playback. Please change the settings in the configuration. Ocurrió un error en la reproducción de audio. Instrument %1 Instrumento %1 Open instrument Abrir instrumento The number of instruments has reached the upper limit. El número de instrumentos ha alcanzado el límite superior. Save instrument Guardar instrumento Open bank Abrir banco Select instruments to load: Seleccionar instrumentos para cargar: Select instruments to save: Seleccionar instrumentos para guardar: Save bank Guardar banco Untitled Sin título - - Custom Personalizado PC-9821 with PC-9801-86 PC-9821 con PC-9801-86 PC-9821 with Speak Board PC-9821 con Speak Board PC-88VA2 PC-88VA2 NEC PC-8801mkIISR NEC PC-8801mkIISR Standard Estándar FM3ch expanded FM3ch expandido Warning Advertencia Insufficient memory size to load ADPCM samples. Please delete the unused samples. Tamaño de memoria insuficiente para cargar muestras ADPCM. The module has been changed. Do you want to save it? El módulo ha sido modificado. ¿Quiere guardarlo? Failed to backup module. Falló al hacer una copia de seguridad del módulo. No instrument No hay instrumento Instrument: %1 Instrumento: %1 Do you want to change song properties? ¿Quiere cambiar las propiedades de la canción? Change to edit mode Cambiar a modo de edición Change to jam mode Cambiar a modo jam YM2608 Music Tracker YM2608 Music Tracker Web: This software is licensed under the GNU General Public License v2.0 or later. Este software está licenciado bajo la GNU General Public License v2.0 o posterior. Source is available at: Fuente está disponible en: Libraries: Librerías: Also see changelog which lists contributors. También ver el registro de cambios que enumera contribuyentes. Thank you to everyone who reports bugs, makes suggestions, and contributes to this project! ¡Gracias a todos los que reportan errores, hacen sugerencias, y contribuyen a este proyecto! blip_buf by (C) Shay Green (LGPL v2.1) blip_buf por (C) Shay Green (LGPL v2.1) C86CTL by (C) honet (BSD 3-Clause) C86CTL por (C) honet (BSD 3-Clause) emu2149 by (C) Mitsutaka Okazaki (MIT License) emu2149 por (C) Mitsutaka Okazaki (MIT License) fmopn by (C) Tatsuyuki Satoh, Jarek Burczynski, ValleyBell (GPL v2+) fmopn por (C) Tatsuyuki Satoh, Jarek Burczynski, ValleyBell (GPL v2+) libOPNMIDI by (C) Vitaly Novichkov (MIT License part) libOPNMIDI por (C) Vitaly Novichkov (MIT License part) Nuked OPN-MOD by (C) Alexey Khokholov (Nuke.YKT), Jean Pierre Cimalando (LGPL v2.1+) Nuked OPN-MOD por (C) Alexey Khokholov (Nuke.YKT), Jean Pierre Cimalando (LGPL v2.1+) Qt (GPL v2+ or LGPL v3) Qt (GPL v2+ o LGPL v3) RtAudio by (C) Gary P. Scavone (RtAudio License) RtAudio por (C) Gary P. Scavone (RtAudio License) RtMidi by (C) Gary P. Scavone (RtMidi License) RtMidi por (C) Gary P. Scavone (RtMidi License) SCCI by (C) gasshi (SCCI License) SCCI por (C) gasshi (SCCI License) Silk icons by (C) Mark James (CC BY 2.5 or 3.0) Silk icons por (C) Mark James (CC BY 2.5 or 3.0) ymdeltat by (C) Tatsuyuki Satoh, Jarek Burczynski, ValleyBell (GPL v2+) ymdeltat por (C) Tatsuyuki Satoh, Jarek Burczynski, ValleyBell (GPL v2+) ymfm by (C) Aaron Giles (BSD 3-Clause) ymfm por (C) Aaron Giles (BSD 3-Clause) About Acerca de Save module Guardar módulo BambooTracker module (*.btm) Módulo de BambooTracker (*.btm) All files (*) Todos los archivos (*) Open module Abrir módulo Do you want to remove all unused instruments? ¿Quiere eliminar todos los instrumentos no utilizados? Do you want to remove all unused patterns? ¿Quiere eliminar todos los patrones no utilizados? Export to WAV Exportar como WAV WAV signed 16-bit PCM (*.wav) WAV con signo 16-bit PCM (*.wav) Cancel Cancelar Export %1 to WAV Exportar %1 como WAV Export to VGM Exportar como VGM VGM file (*.vgm) Archivo VGM (*.vgm) Export to S98 Exportar como S98 S98 file (*.s98) Archivo S98 (*.98) Do you want to remove all duplicate instruments? ¿Quiere eliminar todos los instrumentos duplicados? Do you want to remove all unused ADPCM samples? ¿Quiere eliminar todos las muestras ADPCM no utilizadas? Do you want to transpose a song? ¿Quiere transponer una canción? Do you want to swap tracks? ¿Quiere intercambiar pistas? Approximate song length: %1m%2s Duración de canción aproximada: %1m%2s %1 If you execute this command, the command history is reset. %1 Si ejecuta este comando, el historial de comandos se reiniciará. Could not open the audio stream. Please change the sound settings in Configuration. No se pudo abrir el flujo de audio. Por favor cambie la configuración de sonido en Configuración. Could not set the sample rate of the audio stream to %1Hz. Currently the stream runs on %2Hz instead. No se pudo establecer la frecuencia de muestreo del flujo de audio a %1Hz. Actualmente el flujo corre a %2Hz. Could not initialize MIDI input. No se pudo inicializar la entrada MIDI. ModulePropertiesDialog Module properties Propiedades del módulo Tick frequency Frequencia del tick 60Hz (NTSC) 60Hz (NTSC) 50Hz (PAL) 50Hz (PAL) Custom Personalizado Mixer Mezclador PC-9821 with PC-9801-86 PC-9821 con PC-9801-86 PC-9821 with Speak Board PC-9821 con Speak Board PC-88VA2 PC-88VA2 PC-8801mkIISR PC-8801mkIISR Custom mixer Mezclador personalizado FM FM SSG SSG Set Establecer Song control Control de canción Song Canción Title Título Song type Tipo de canción Insert Insertar Update Actualizar Untitled Sin título Number Número Remove Eliminar ModuleSaveCheckDialog Save changes to %1? ¿Guardar cambios en %1? Untitled Sin título NotationSystem English Inglés German Alemán OrderListPanel &Insert Order &Insertar orden &Remove Order Eliminar orden(&R) &Duplicate Order &Duplicar orden &Clone Patterns &Clonar patrones Clone &Order Clonar &Orden Move Order &Up Mover orden hacia arriba(&U) Move Order Do&wn Mover orden hacia abajo(&W) Cop&y Copiar(&Y) &Paste &Pegar PanMacroEditor Right Derecha Left Izquierda Panning Left Izquierda Center Centro Right Derecha PatternEditorPanel &Undo Deshacer(&U) &Redo &Rehacer &Copy &Copiar Cu&t Cor&tar &Paste &Pegar Paste Specia&l Pegar Especial(&I) &Mix &Mezclar &Overwrite Sobrescribir(&O) &Insert &Insertar &Erase Borrar(&E) Select &All Seleccionar todo(&A) Patter&n Patró&n &Interpolate &Interpolar &Reverse &Revertir R&eplace Instrument R&eemplazar instrumento E&xpand E&xpandir S&hrink Encoger(&H) &Transpose &Transponer &Decrease Note &Disminuir nota &Increase Note &Incrementar nota D&ecrease Octave Disminuir octava(&E) I&ncrease Octave I&ncrementar octava &Change Values &Cambiar valores Fine &Decrease Values &Disminuir el valor en 1 Fine &Increase Values &Incrementar el valor en 1 Coarse D&ecrease Values Disminuir el valor en 16(&E) Coarse I&ncrease Values I&ncrementar el valor en 16 To&ggle Track Reproducir pista(&G) &Solo Track Pista &Solista Expand E&ffect Column Expandir columna de e&fectos Shrin&k Effect Column Encoger columna de efectos(&K) &Unmute All Tracks Desilenciar todas las pistas(&U) QObject Error Error An unknown error occurred. %1 Ocurrió un error desconocido. %1 An unknown error occurred. Ocurrió un error desconocido. S98ExportSettingsDialog S98 export settings Configuración de exportación S98 Resolution Resolución Tag Etiqueta Title Título Artist Artista Game Juego Year Año Genre Género Comment Comentario Copyright Derechos de autor S98by S98by System Sistema NEC PC-9801 NEC PC-9801 Target Fuente de sonido de destino FM FM YM2608 OPNA YM2608 OPNA YM2612 OPN2 YM2612 OPN2 YM2203 OPN YM2203 OPN None Ninguno SSG SSG OPN internal OPN interno AY-3-8910 PSG AY-3-8910 PSG YM2149 PSG YM2149 PSG Support Lista de compatibilidad FM Channels Canales FM Yes Rhythm Ritmo ADPCM ADPCM No No SampleLengthDialog Resize Cambiar tamaño Length Largo SongType Standard Estándar FM3ch expanded FM3ch expandido SsgInstrumentEditor Waveform Forma de onda Users: Usuarios: Square mask Máscara de onda cuadrada Raw Valor de registro Tone/Mask ratio Relación Tono/Máscara Tone/Noise Tono/Ruido Envelope Envolvente Hardware envelope frequency Frecuencia de envolvente de hardware Tone/Env ratio Relación Tono/Env Arpeggio Arpegio Type: Tipo: Pitch Tono Sq Sq Tri Tri Saw Saw InvSaw InvSaw SMTri SMTri SMSaw SMSaw SMInvSaw SMInvSaw HEnv %1 HEnv %1 Absolute Absoluta Fixed Fija Relative Relativa SwapTracksDialog Swap Tracks Intercambiar pistas Track 1 Pista 1 Track 2 Pista 2 ToneNoiseMacroEditor Tone Tono Noise Ruido TransposeSongDialog Transpose Song Transponer canción Semitones Semitonos Exclude these instruments Excluir estos instrumentos Reverse Revertir Clear All Borrar todo VgmExportSettingsDialog VGM export settings Configuración de exportación VGM GD3 tag Etiqueta GD3 Game Juego Name Nombre English Inglés Release date Fecha de lanzamiento Japanese Japonés System Sistema NEC PC-9801 NEC PC-9801 Track Pista Title Título Author Autor VGM file Archivo VGM Creator Creador Notes Notas Target Fuente de sonido de destino FM FM YM2608 OPNA YM2608 OPNA YM2612 OPN2 YM2612 OPN2 YM2203 OPN YM2203 OPN YM2610B OPNB YM2610B OPNB None Ninguno SSG SSG OPN internal OPN interno AY-3-8910 PSG AY-3-8910 PSG YM2149 PSG YM2149 PSG Mix Mezcla Gain Ganancia Support Lista de compatibilidad FM Channels Canales FM Yes Rhythm Ritmo ADPCM ADPCM No No VisualizedInstrumentMacroEditor Size: 1 Tamaño: 1 Size: %1 Tamaño: %1 Release Soltar Loop Número de repeticiones Loop %1 Número de repeticiones %1 Fixed Fijo Absolute Absoluto Relative Relativo WaveExportSettingsDialog WAV export settings Configuración de exportación WAV Loop Número de repeticiones Sample rate Frecuencia de muestreo Separate track export Exportar pistas por separado Reverse Revetir Check All Marcar todo BambooTracker-0.6.5/BambooTracker/lang/bamboo_tracker_fr.ts000066400000000000000000006442271476276175200237420ustar00rootroot00000000000000 ADPCMSampleEditor Users: Utilisateurs : Memory: Repeat Root key Key C Do C# Do# D D# Ré# E Mi F Fa F# Fa# G Sol G# Sol# A La A# La# B Si Rate &Resize Re&verse Zoom I&n Zoom &Out &Import &Clear &Effacer &Grid View Grid &Settings... &Draw Sample Direc&t Draw Error Erreur Supported sample rate is 2kHz-55.5kHz, but the rate of selected sample is %1. The selected sample is not mono channel. Import sample WAV signed 16-bit PCM (*.wav) All files (*) BookmarkManagerForm Bookmark Manager Sort by Position Name Nom Remove Supprimer Clear All Bookmark editor Order Step Create New Update Bookmark Bookmark %1 CommentEditDialog Module Comment Commentaire du module ConfigurationDialog Configuration Configuration General Général Fill 00 to effect value Emulation core Cœur d'émulation API MIDI MIDI MIDI input Entrée MIDI Audio output The level setting for each part is valid when the mixer in the module properties is not checked. Appearance Font and size Order list rows Pattern editor rows Pattern editor header Order list header Colors Item Color Pattern editor Default step text Default step background Highlighted step 1 background Highlighted step 2 background Current step text Current step background Current editing step background Current cell background Current playing step background Selection background Hovered cell background Default step number Highlighted step 1 number Highlighted step 2 number Note text Instrument text Volume text Effect text Error text Header text Header background Mask Border Header border Mute Unmute Background Marker Unfocused shadow Order list Default row text Default row background Current row text Current row background Current editing row background Current playing row background Row number Instrument list Default text Selected background Hovered background Selected hovered background Oscilloscope Foreground Save Load Formats Formats Keys Touches Shortcuts Action Note entry layout Agencement pour la saisie de notes Custom Personnalisé Low Bas C Do C# Do# D D# Ré# E Mi F Fa F# Fa# G Sol G# Sol# A La A# La# B Si High Haut Special keys Touches spéciales Key off Relâchement de touche Octave up Octave supérieure Octave down Octave inférieure Echo buffer Tampon d'écho Edit settings Paramètres d'édition Page jump length Longueur du saut de page General settings Paramètres généraux Warp cursor J'ai rien trouvé de mieux. Déplacer le curseur de manière cyclique Warp across orders J'ai rien trouvé de mieux. Incertitude à propos de la traduction adéquate pour "order" Se déplacer de manière cyclique le long des ordres Show row numbers in hex Numéroter les rangées en hexa Preview previous/next orders Incertitude à propos de la traduction adéquate pour "order" Prévisualiser les ordres précédent/suivant Backup modules Sauvegarder les modules Don't select on double click Ne pas sélectionner avec un double clic Reverse FM volume order Inverser la valeur du volume FM Move cursor to right Déplacer le curseur vers la droite Retrieve channel state Récupérer l'état des canaux Enable translation Activer la traduction Show FM detune as signed Afficher le désaccordage FM comme valeur signée Show wave visual Afficher une visualisation de l'onde Write only used samples Reflect instrument number change Fix jamming volume Mute hidden tracks Restore track visibility Description: Description : Wave view Frame rate Wrap cursor Wrap across orders Move cursor right Move cursor with horizontal scroll bar Overwrite unused and unedited properties Note names Notation system Sound Son Sample rate Taux d'échantillonnage Buffer length Taille de la mémoire tampon 1ms 1ms Device Périphérique Use SCCI (beta) Utiliser SCCI (bêta) FM envelope text Texte d'enveloppe FM Add Ajouter Edit Éditer Remove Supprimer Mixer Mélangeur Real chip interface Part Partie Reset Réinitialiser Virtual port Port virtuel Master Maître Wrap the cursor around the edges of the pattern editor. Move to the previous or next order when reaching the top or bottom in the pattern editor. Preview the previous and next orders in the pattern editor. Automatically move the cursor right after entering effects in the pattern editor. Automatically fill 00 to the effect value column upon entering an effect ID. Move the cursor with the horizontal scroll bar in the order list and the pattern editor. Overwrite unused and unedited instrument properties when creating new properties. If disabled, override unused properties regardless of editing. Only send samples used by instruments to the ADPCM memory. It is recommended to turn this off if you change ADPCM samples frequently due to the high cost of rewriting.. Automatically update the instrument number in patterns when an instrument's number is changed. Automatically mute tracks when they are hidden. Transpose down one semitone Transpose up one semitone Transpose down one octave Transpose up one octave The change of emulator will be effective after restarting the program. Le changement d'émulateur sera pris en compte au redémarrage du logiciel. Warp the cursor around the edges of the pattern editor. J'ai rien trouvé de mieux. Le curseur se déplace de manière cyclique à proximité des bords de l'éditeur de motifs. Move to previous or next order when reaching top or bottom in the pattern editor. Incertitude à propos de la traduction adéquate pour "order" Se positionner sur l'ordre précédent ou suivant lorsque le sommet ou le bas de l'éditeur de motifs est atteint. Display order numbers and the order count on the status bar in hexadecimal. Incertitude à propos de la traduction adéquate pour "order" Afficher le numéro d'ordre et le nombre d'ordres en hexadécimal dans la barre d'état. Display row numbers and the playback position on the status bar in hexadecimal. Preview previous and next orders in the pattern editor. Incertitude à propos de la traduction adéquate pour "order" Prévisualiser les ordres précédent et suivant dans l'éditeur de motifs. Create a backup copy of the existing file when saving a module. Créer une copie de sauvegarde du fichier existant lors de la sauvegarde d'un module. Don't select the whole track when double-clicking in the pattern editor. Ne pas sélectionner la piste entière lors d'un double-clic dans l'éditeur de motif. Reverse the order of FM volume so that 00 is the quietest in the pattern editor. Inverser la valeur du volume FM pour que 00 soit le plus bas dans l'éditeur de motif. Move the cursor to right after entering effects in the pattern editor. Déplacer le curseur vers la droite après être entré dans les effets dans l'éditeur de motif. Reconstruct the current channel's state from previous orders upon playing. Reconstruire l'état du canal actuel à partir des ordres précédents durant la lecture. Translate to your language from the next launch. See readme to check supported languages. Traduire en votre langue au prochain démarrage. Lire le fichier readme pour connaître la liste des langues gérées. Display FM detune values as signed numbers in the FM envelope editor. Afficher les valeurs de désaccordage FM comme nombres signés dans l'éditeur d'enveloppe FM. Enable an oscilloscope which displays a waveform of the sound output. Activer un oscilloscope qui affiche la forme d'onde du signal de sortie. Set maximum volume during jam mode. When unchecked, the volume is changed by the volume spinbox. Restore the previous track visibility on startup. Play and stop Play Play from start Play pattern Play from cursor Play from marker Play step Stop Focus on pattern editor Focus on order list Focus on instrument list Toggle edit/jam mode Set marker Paste and mix Paste and overwrite Paste and insert Select all Deselect Select row Select column Select pattern Select order Go to step Toggle track Solo track Interpolate Reverse Go to previous order Go to next order Toggle bookmark Previous bookmark Next bookmark Previous instrument Next instrument Mask instrument Mask volume Edit instrument Follow mode Duplicate order Clone patterns Clone order Replace instrument Expand pattern Shrink pattern Fine decrease values Fine increase values Coarse decrease values Coarse increase valuse Expand effect column Shrink effect column Previous highlighted step Next highlighted step Increase pattern size Decrease pattern size Increase edit step Decrease edit step Display effect list Previous song Next song Jam volume up Jam volume down None Description: %1 Set %1 Description: Description : Open color scheme ini file (*.ini) All files (*) Error Erreur An unknown error occurred while loading the color scheme. Save color scheme Failed to save the color scheme. EffectDescription Arpeggio, x: 2nd note (0-F), y: 3rd note (0-F) Arpège, x : 2nde note (0-F), y : 3ème note (0-F) Portamento up, xx: depth (00-FF) Portamento haut, xx : profondeur (00-FF) Portamento down, xx: depth (00-FF) Portamento bas, xx : profondeur (00-FF) Tone portamento, xx: depth (00-FF) Portamento de tonalité, xx : profondeur (00-FF) Vibrato, x: period (0-F), y: depth (0-F) Vibrato, x : période (0-F), y : profondeur (0-F) Tremolo, x: period (0-F), y: depth (0-F) Trémolo, x : période (0-F), y : profondeur (0-F) Pan, xx: 00 = no sound, 01 = right, 02 = left, 03 = center Pan, xx : 00 = pas de son, 01 = droite, 02 = gauche, 03 = centre Volume slide, x: up (0-F), y: down (0-F) Glissé de volume, x : haut (0-F), y : bas (0-F) Jump to beginning of order xx Aller au début de la commande xx End of song Fin du morceau Jump to step xx of next order Aller au pas xx de la prochaine commande Change speed (xx: 00-1F), change tempo (xx: 20-FF) Changement de vitesse (xx : 00-1F), changement de tempo (xx : 20-FF) Note delay, xx: count (00-FF) Délai de note, xx : compte (00-FF) Auto envelope, x: shift amount (0-F), y: shape (0-F) Hardware envelope period 1, xx: high byte (00-FF) Hardware envelope period 2, xx: low byte (00-FF) Retrigger, x: volume slide (0-7: up, 8-F: down), y: tick (1-F) Set groove xx Paramétrer le groove xx Detune, xx: pitch (00-FF) Désaccordage, xx : tonalité (00-FF) Note slide up, x: count (0-F), y: seminote (0-F) Note glissée vers le haut, x : compte (0-F), y : demi-note (0-F) Note slide down, x: count (0-F), y: seminote (0-F) Note glissée vers le bas, x : compte (0-F), y : demi-note (0-F) Note cut, xx: count (00-FF) Coupure de note, xx : compte (01-FF) {00-?} Transpose delay, x: count (0-7: up, 8-F: down), y: seminote (0-F) Délai de transposition, x : compte (1-7 : haut, 9-F : bas), y : demi-note (0-F) {0-7:?} {8-?} {0-?} Volume delay, x: count (0-F), yy: volume (00-FF) Délai de volume, x : compte (1-F), yy : volume (00-FF) {0-?} {00-?} Note cut, xx: count (01-FF) Coupure de note, xx : compte (01-FF) Transpose delay, x: count (1-7: up, 9-F: down), y: seminote (0-F) Délai de transposition, x : compte (1-7 : haut, 9-F : bas), y : demi-note (0-F) Tone/Noise mix, xx: 00 = no sound, 01 = tone, 02 = noise, 03 = tone & noise Master volume, xx: volume (00-3F) Volume maître, xx : volume (00-3F) Noise pitch, xx: pitch (00-1F) Register address bank 0, xx: address (00-6B) Register address bank 1, xx: address (00-6B) Register value set, xx: value (00-FF) AR control, x: operator (1-4), yy: attack rate (00-1F) Brightness, xx: relative value (01-FF) DR control, x: operator (1-4), yy: decay rate (00-1F) Envelope reset, xx: count (00-FF) FB control, xx: feedback value (00-07) Fine detune, xx: pitch (00-FF) ML control, x: operator (1-4), y: multiple (0-F) Volume delay, x: count (1-F), yy: volume (00-FF) Délai de volume, x : compte (1-F), yy : volume (00-FF) RR control, x: operator (1-4), y: release rate (0-F) TL control, x: operator (1-4), yy: total level (00-7F) Invalid effect Effet invalide EffectListDialog Effect list Liste d'effets Effect Effet Track type Type de piste Description Description Arpeggio, x: 2nd note (0-F), y: 3rd note (0-F) Arpège, x : 2nde note (0-F), y : 3ème note (0-F) Portamento up, xx: depth (00-FF) Portamento haut, xx : profondeur (00-FF) Portamento down, xx: depth (00-FF) Portamento bas, xx : profondeur (00-FF) Tone portamento, xx: depth (00-FF) Portamento de tonalité, xx : profondeur (00-FF) Vibrato, x: period (0-F), y: depth (0-F) Vibrato, x : période (0-F), y : profondeur (0-F) Tremolo, x: period (0-F), y: depth (0-F) Trémolo, x : période (0-F), y : profondeur (0-F) Pan, xx: 00 = no sound, 01 = right, 02 = left, 03 = center Pan, xx : 00 = pas de son, 01 = droite, 02 = gauche, 03 = centre Volume slide, x: up (0-F), y: down (0-F) Glissé de volume, x : haut (0-F), y : bas (0-F) Jump to begginning of order xx Aller au début de la commande xx Jump to beginning of order xx Aller au début de la commande xx End of song Fin du morceau Jump to step xx of next order Aller au pas xx de la prochaine commande Change speed (xx: 00-1F), change tempo (xx: 20-FF) Changement de vitesse (xx : 00-1F), changement de tempo (xx : 20-FF) Note delay, xx: count (00-FF) Délai de note, xx : compte (00-FF) Set groove xx Paramétrer le groove xx Detune, xx: pitch (00-FF) Désaccordage, xx : tonalité (00-FF) Note slide up, x: count (0-F), y: seminote (0-F) Note glissée vers le haut, x : compte (0-F), y : demi-note (0-F) Note slide down, x: count (0-F), y: seminote (0-F) Note glissée vers le bas, x : compte (0-F), y : demi-note (0-F) Note cut, xx: count (01-FF) Coupure de note, xx : compte (01-FF) Transpose delay, x: count (1-7: up, 9-F: down), y: seminote (0-F) Délai de transposition, x : compte (1-7 : haut, 9-F : bas), y : demi-note (0-F) Master volume, xx: volume (00-3F) Volume maître, xx : volume (00-3F) Volume delay, x: count (1-F), yy: volume (00-FF) Délai de volume, x : compte (1-F), yy : volume (00-FF) Rhythm Rythme FMEnvelopeSetEditDialog Edit FM envelope set Éditer le format d'enveloppe FM Add Ajouter Set digit types in the order of appearence: Définir les types de valeurs d'après l'ordre d'apparition : Set digit types in the order of appearance. Remove Supprimer Number Numéro Type Type Skip FMOperatorTable Operator Opérateur SSGEG SSGEG Type Type Operator %1 Copy envelope Copier l'enveloppe Paste envelope Coller l'enveloppe Paste envelope From Coller l'enveloppe depuis Copy operator Copier l'opérateur Paste operator Coller l'opérateur FileIOErrorMessageBox Path does not exist. Unsupported file format. Could not load the %1 properly. Please make sure that you have the latest version of BambooTracker. Could not load the %1. It may be corrupted. Stopped at %2. Failed to load %1. Failed to export to %1. Failed to save the %1. Error Erreur Could not open the file. FileType module s98 vgm wav bank instrument GoToDialog Go To Order Step Track Piste GridSettingsDialog Grid Settings Interval GrooveSettingsDialog Groove Settings Paramètres du groove Remove Supprimer Add Ajouter Sequence Séquence Copy Fxx Copier Fxx Expand Étendre Generate Générer Shrink Réduire Pad Remplir Speed: Vitesse : Speed: %1 HideTracksDialog Hide Tracks Check All Reverse Set the visibility of tracks: InstrumentEditorADPCMForm Users: Utilisateurs : C Do C# Do# D D# Ré# E Mi F Fa F# Fa# G Sol G# Sol# A La A# La# B Si Sample Envelope Enveloppe Arpeggio Arpège Type: Type : Pitch Panning Absolute Absolu Fixed Fixe Relative Relatif Error Erreur InstrumentEditorDrumkitForm Sample assignment Key # # Pitch Pan Panning Sample InstrumentEditorFMForm Envelope Enveloppe Users: Utilisateurs : LFO/Operator sequence Incertitude sur ce qu'est vraiment "operator sequence". À vérifier LFO/Séquence d'opérateurs LFO LFO Start count: Compteur initial : AM operators Opérateurs AM Operator 1 Opérateur 1 Operator 2 Opérateur 2 Operator 3 Opérateur 3 Operator 4 Opérateur 4 Envelope reset Réinitialisation d'enveloppe Reset envelope before key on Abrégé pour rentrer dans le cadre Réinitialiser l'env. en début de note Panning Operator sequence Incertitude sur ce qu'est vraiment "operator sequence". À vérifier Séquence d'opérateurs Operator: Opérateur : Sequence Séquence FM 3ch FM 3canaux Arpeggio/Pitch Arpège / hauteur tonale Arpeggio Arpège Type: Type : Pitch Hauteur tonale Absolute Absolu Fix Fixe Freq All Fixed Fixe Relative Relatif Error Erreur Did not match the clipboard text format with %1. Impossible d'interpréter le texte formaté du presse-papiers avec %1. Copy envelope Copier l'enveloppe Paste envelope Coller l'enveloppe Paste envelope From Coller l'enveloppe depuis Copy LFO parameters Copier les paramètres du LFO Paste LFO parameters Coller les paramètres du LFO InstrumentEditorSSGForm Wave form Forme d'onde Users: Utilisateurs : Square mask À vérifier Masque carré Raw À vérifier Brut Tone/Mask ratio À vérifier Rapport tonalité/masque Hard frequency À vérifier... Fréquence dure Tone/Hard ratio À vérifier... Rapport tonalité/dur Waveform Tone/Noise Tonalité/bruit Envelope Enveloppe Hardware envelope frequency Tone/Env ratio Arpeggio Arpège Type: Type : Pitch Tonalité Sq Tri Saw InvSaw SMTri SMSaw SMInvSaw HEnv %1 Absolute Absolu Fixed Fixe Fix Fixe Relative Relatif InstrumentSelectionDialog Select instruments Sélectionner les instruments Search... Rechercher... KeySignatureManagerForm Key Signature Manager Key signature editor Key Order Step Create New Update Remove Supprimer Clear All KeyboardShortcutListDialog Keyboard shortcuts Raccourcis clavier qrc:/doc/shortcuts MainWindow BambooTracker BambooTracker Order List Incertitude à propos de la traduction adéquate pour "order" Liste d'ordres Module Settings Paramètres du module Copyright Droits d'auteur Author Auteur Module Title Titre du module Edit settings Paramètres d'édition Editable step Intervalle éditable Key repetition Répétition des touches Song Morceau # # Song Settings Paramètres du morceau Speed Vitesse Pattern Size Taille de motif Tempo Tempo Style Style Untitled Sans titre Groove Groove Instruments Instruments &File &Fichier &Export &Exporter &Recent Files Fichiers &récents &Edit &Édition &Select &Sélectionner Paste Specia&l Collage spécia&l Patter&n &Motif Son&g Mor&ceau &Tracker &Tracker &Pattern &Motif &Transpose &Transposer Pattern size Step Title Titre Songs &Bookmarks &Change Values S&ong &Module &Module Clean&up &Nettoyer &Instrument &Instrument &Help &Aide Vie&w Main toolbar Barre d'outils principale Secondary toolbar Barre d'outils secondaire &New... &Nouveau... Ctrl+N Ctrl+N &Open... &Ouvrir... Ctrl+O Ctrl+O &Save &Sauvegarder Ctrl+S Ctrl+S Save &As... S&auvegarder sous... E&xit &Quitter &Undo Ann&uler Ctrl+Z Ctrl+Z &Redo &Rétablir Ctrl+Y Ctrl+Y Cu&t Cou&per Ctrl+X Ctrl+X &Copy &Copier Ctrl+C Ctrl+C &Paste Co&ller Ctrl+V Ctrl+V &Delete &Supprimer Del Suppr &All &Tout Ctrl+A Ctrl+A &None &Aucun Esc Échap E&xpand É&tendre S&hrink &Réduire &Decrease Note &Diminuer la note Ctrl+F1 Ctrl+F1 &Increase Note &Augmenter la note Ctrl+F2 Ctrl+F2 D&ecrease Octave Diminu&er l'octave Ctrl+F3 Ctrl+F3 I&ncrease Octave Augme&nter l'octave Ctrl+F4 Ctrl+F4 &Insert Order Incertitude à propos de la traduction adéquate pour "order" &Insérer un ordre &Remove Order Incertitude à propos de la traduction adéquate pour "order" Supp&rimer l'ordre &Module Properties... Propriétés du &module... Ctrl+P Ctrl+P &New Instrument &Nouvel instrument &Remove Instrument Supp&rimer l'instrument &Clone Instrument &Cloner l'instrument &Deep Clone Instrument Cloner l'instrument en profon&deur &Load From File... &Charger depuis un fichier... &Save To File... &Sauvegarder vers un fichier... &Edit... &Éditer... Ctrl+I Ctrl+I &Play &Jouer Play P&attern Jouer le &motif F6 F6 Play &From Start Jouer &depuis le début F5 F5 Play From C&ursor Jouer depuis le c&urseur F7 F7 &Stop &Arrêter F8 F8 &Edit Mode Mode &édition Space Espace To&ggle Track &Basculer la piste Alt+F9 Alt+F9 S&olo Track Mettre la piste en s&olo Alt+F10 Alt+F10 &Kill Sound &Tuer le son F12 F12 &About... À &propos... Fo&llow Mode Mode &suivi ScrollLock ArrêtDéfil &Groove Settings... Paramètres du &groove... &Configuration... &Configuration... &Duplicate Order Incertitude à propos de la traduction adéquate pour "order" &Dupliquer l'ordre Ctrl+D Ctrl+D Move Order &Up Incertitude à propos de la traduction adéquate pour "order" Déplacer l'ordre vers le ha&ut Move Order Do&wn Incertitude à propos de la traduction adéquate pour "order" Déplacer l'ordre vers le &bas &Clone Patterns &Cloner les motifs Alt+D Alt+D Clone &Order Incertitude à propos de la traduction adéquate pour "order" Cloner l'&ordre &Comments... &Commentaires... &Interpolate &Interpoler Ctrl+G Ctrl+G &Reverse Inve&rser Ctrl+R Ctrl+R R&eplace Instrument R&emplacer l'instrument Alt+S Alt+S &Row &Rangée &Column &Colonne &Order Incertitude à propos de la traduction adéquate pour "order" &Ordre Remove Unused &Instruments Supprimer les &instruments inutilisés Remove Unused &Patterns Sup&primer les motifs inutilisés &WAV... &WAV... &VGM... &VGM... &Mix &Mélange Ctrl+M Ctrl+M &Overwrite É&craser &Import From Bank File... &Importer depuis un fichier de banque... &S98... &S98... &Clear &Effacer &Effect List... Liste d'&effets... Effect List Liste d'effets F1 F1 &Shortcuts... &Raccourcis... E&xport To Bank File... Remove &Duplicate Instruments Re&name Instrument &Bookmark Manager... Fine &Decrease Values Shift+F1 Fine &Increase Values Shift+F2 Coarse D&ecrease Values Shift+F3 Coarse I&ncrease Values Shift+F4 &Toggle Bookmark Ctrl+K &Next Bookmark Ctrl+PgDown &Previous Bookmark Ctrl+PgUp &Instrument Mask &Volume Mask Set Ro&w Marker Ctrl+B Play From &Marker Ctrl+F7 Ctrl+F7 &Go To... Alt+G Remove Unused &ADPCM Samples &Status Bar &Toolbar New Drumki&t &Wave View &Transpose Song... &Swap Tracks... &Insert &Cursor &Selection &Fill &Hide Tracks... &Estimate Song Length... &Key Signature Manager... &Welcome... Octave Octave Octave: %1 Octave : %1 Save changes to %1? Sauvegarder les modifications dans %1 ? Welcome to BambooTracker v%1! Error Erreur Instrument %1 Instrument %1 Open instrument Ouvrir l'instrument Failed to load instrument. Le chargement de l'instrument a échoué. Save instrument Sauvegarder l'instrument Open bank Ouvrir la banque Select instruments to load: Sélectionnez les instruments à charger : No instrument Pas d'instrument Standard Standard English Default notation system Set the name of suitable notation system (English or German) Anglais Volume Step highlight 1st 1er intervalle en surbrillance 2nd 2nd BambooTracker instrument (*.bti) Instrument BambooTracker (*.bti) DefleMask preset (*.dmp) Préréglage DefleMask (*.dmp) TFM Music Maker instrument (*.tfi) Instrument TFM Music Maker (*.tfi) VGM Music Maker instrument (*.vgi) Instrument VGM Music Maker (*.vgi) WOPN instrument (*.opni) Instrument WOPN (*.opni) Gens KMod dump (*.y12) Décharge Gens KMod (*.y12) MVSTracker instrument (*.ins) Instrument MVSTracker (*.ins) BambooTracker instrument file (*.bti) Fichier instrument BambooTracker (*.bti) WOPN bank (*.wopn) Banque WOPN (*.wopn) Select instruments to save: Save bank - Custom PC-9821 with PC-9801-86 PC-9821 with Speak Board PC-88VA2 NEC PC-8801mkIISR FM3ch expanded FM3canaux étendu Insufficient memory size to load ADPCM samples. Please delete the unused samples. The module has been changed. Do you want to save it? Could not initialize MIDI input. Instrument: %1 BambooTracker module (*.btm) All files (*) WAV signed 16-bit PCM (*.wav) Export %1 to WAV VGM file (*.vgm) S98 file (*.s98) Do you want to remove all duplicate instruments? Do you want to remove all unused ADPCM samples? Do you want to transpose a song? Do you want to swap tracks? Approximate song length: %1m%2s &Add &Ajouter &Remove Supp&rimer Edit &name Éditer le &nom &Clone &Cloner &Deep clone Cloner en &profondeur &Load from file... &Charger depuis un fichier... &Save to file... &Sauvegarder dans le fichier... &Import from bank file... &Importer depuis un fichier de banque... <b>YM2608 (OPNA) Music Tracker<br>Copyright (C) 2018, 2019 Rerrah</b><br><hr>Libraries:<br>- libOPNMIDI by (C) Vitaly Novichkov (MIT License part)<br>- MAME (MAME License)<br>- nowide by (C) Artyom Beilis (BSL v1.0)<br>- Nuked OPN-MOD by (C) Alexey Khokholov (Nuke.YKT)<br>and (C) Jean Pierre Cimalando (LGPL v2.1)<br>- RtMidi by (C) Gary P. Scavone (RtMidi License)<br>- SCCI (SCCI License)<br>- Silk icon set 1.3 by (C) Mark James (CC BY 2.5)<br>- Qt (GPL v2+ or LGPL v3)<br>- VGMPlay by (C) Valley Bell (GPL v2)<br><br>Also see changelog which lists contributors. <b>YM2608 (OPNA) Music Tracker<br>Copyright (C) 2018, 2019 Rerrah</b><br><hr>Bibliothèques:<br>- libOPNMIDI par (C) Vitaly Novichkov (en partie sous licence MIT)<br>- MAME (licence MAME)<br>- nowide par (C) Artyom Beilis (BSL v1.0)<br>- Nuked OPN-MOD par (C) Alexey Khokholov (Nuke.YKT)<br>et (C) Jean Pierre Cimalando (LGPL v2.1)<br>- RtMidi par (C) Gary P. Scavone (licence RtMidi)<br>- SCCI (licence SCCI)<br>- ensemble d'icône Silk 1.3 par (C) Mark James (CC BY 2.5)<br>- Qt (GPL v2+ ou LGPL v3)<br>- VGMPlay par (C) Valley Bell (GPL v2)<br><br>Lire également le changelog qui contient la liste des contributeurs. Instrument: Instrument : Do you want to change song properties? Voulez-vous modifier les propriétés du morceau ? Change to jam mode Passage en mode jeu Change to edit mode Passage en mode édition About À propos Failed to backup module. La sauvegarde du module a échoué. Welcome to BambooTracker! Don't know where to start? Check the demo modules and instruments included with your download of BambooTracker. Need a list of effects and shortcuts? Check the Help menu at the top of the window. Still lost? The README.md has a link to our Discord server. Think you've found a bug? Missing a feature? BambooTracker is still in development, bugs and missing features are to be expected. So we need your help! Please report any bugs you find and requests and features you'd like to see on our Discord server or our bug tracker (%1). If you're a developer yourself or would like to start being one, consider contributing to the project yourself. Any help would be appreciated! Welcome The number of instruments has reached the upper limit. YM2608 Music Tracker Web: This software is licensed under the GNU General Public License v2.0 or later. Source is available at: Libraries: Also see changelog which lists contributors. Thank you to everyone who reports bugs, makes suggestions, and contributes to this project! C86CTL by (C) honet (BSD 3-Clause) emu2149 by (C) Mitsutaka Okazaki (MIT License) fmopn by (C) Tatsuyuki Satoh, Jarek Burczynski, ValleyBell (GPL v2+) libOPNMIDI by (C) Vitaly Novichkov (MIT License part) Nuked OPN-MOD by (C) Alexey Khokholov (Nuke.YKT), Jean Pierre Cimalando (LGPL v2.1+) Qt (GPL v2+ or LGPL v3) RtAudio by (C) Gary P. Scavone (RtAudio License) RtMidi by (C) Gary P. Scavone (RtMidi License) SCCI by (C) gasshi (SCCI License) Silk icons by (C) Mark James (CC BY 2.5 or 3.0) ymdeltat by (C) Tatsuyuki Satoh, Jarek Burczynski, ValleyBell (GPL v2+) Save module Sauvegarder le module BambooTracker module file (*.btm) Fichier de module BambooTracker (*.btm) Open module Ouvrir le module Do you want to remove all unused instruments? Voulez-vous supprimer tous les instruments inutilisés ? Do you want to remove all unused patterns? Voulez-vous supprimer tous les motifs inutilisés ? Export to wav Exporter en wav Export to WAV Exporter en WAV Cancel Annuler Failed to export to wav file. L'exportation en fichier wav a échoué. Export to vgm Exporter en vgm Export to VGM Exporter en VGM Failed to export to vgm file. L'exportation en fichier vgm a échoué. Export to s98 Exporter en s98 Export to S98 Exporter en S98 Failed to export to s98 file. L'exportation en fichier s98 a échoué. Warning %1 If you execute this command, the command history is reset. Could not open the audio stream. Please change the sound settings in Configuration. Could not set the sample rate of the audio stream to %1Hz. Currently the stream runs on %2Hz instead. ModulePropertiesDialog Module properties Propriétés du module Tick frequency Fréquence d'horloge 60Hz (NTSC) 60Hz (NTSC) 50Hz (PAL) 50Hz (PAL) Custom Autre Mixer Mélangeur PC-9821 with PC-9801-86 PC-9821 with Speak Board PC-88VA2 PC-8801mkIISR Custom mixer FM FM SSG SSG Set Song control Contrôle du morceau Song Morceau Update Remove Supprimer Insert song Insérer un morceau Title: Titre : Song type: Type de morceau : Insert Insérer Untitled Sans titre Number Numéro Title Titre Song type Type de morceau Standard Standard FM3ch expanded FM3canaux étendu ModuleSaveCheckDialog Save changes to %1? Sauvegarder les modifications dans %1 ? Untitled Sans titre NotationSystem English Anglais German OrderListPanel &Insert Order Incertitude à propos de la traduction adéquate pour "order" &Insérer un ordre &Remove Order Incertitude à propos de la traduction adéquate pour "order" Supp&rimer l'ordre &Duplicate Order Incertitude à propos de la traduction adéquate pour "order" &Dupliquer l'ordre &Clone Patterns &Cloner les motifs Clone &Order Incertitude à propos de la traduction adéquate pour "order" Cloner l'&ordre Move Order &Up Incertitude à propos de la traduction adéquate pour "order" Déplacer l'ordre vers le ha&ut Move Order Do&wn Incertitude à propos de la traduction adéquate pour "order" Déplacer l'ordre vers le &bas Cop&y Cop&ier &Paste Co&ller PanMacroEditor Right Left Panning Left Center Right PatternEditorPanel Invalid effect Effet invalide 00xy - Arpeggio, x: 2nd note (0-F), y: 3rd note (0-F) 00xy - Arpège, x : 2nde note (0-F), y : 3ème note (0-F) 01xx - Portamento up, xx: depth (00-FF) 01xx - Portamento haut, xx : profondeur (00-FF) 02xx - Portamento down, xx: depth (00-FF) 02xx - Portamento bas, xx : profondeur (00-FF) 03xx - Tone portamento, xx: depth (00-FF) 03xx - Portamento de tonalité, xx : profondeur (00-FF) 04xy - Vibrato, x: period (0-F), y: depth (0-F) 04xx - Vibrato, x : période (0-F), y : profondeur (0-F) 07xx - Tremolo, x: period (0-F), y: depth (0-F) 07xx - Trémolo, x : période (0-F), y : profondeur (0-F) 08xx - Pan, xx: 00 = no sound, 01 = right, 02 = left, 03 = center 08xx - Pan, xx : 00 = pas de son, 01 = droite, 02 = gauche, 03 = centre 0Axy - Volume slide, x: up (0-F), y: down (0-F) 0Axy - Glissé de volume, x : haut (0-F), y : bas (0-F) 0Bxx - Jump to begginning of order xx 0Bxx - Aller au début de la commande xx 0Cxx - End of song 0Cxx - Fin du morceau 0Dxx - Jump to step xx of next order 0Dxx - Aller au pas xx de la prochaine commande 0Oxx - Set groove xx 0Oxx - Paramétrer le groove xx 0Pxx - Detune, xx: pitch (00-FF) 0Pxx - Désaccordage, xx : toalité (00-FF) 0Qxy - Note slide up, x: count (0-F), y: seminote (0-F) 0Qxy - Note glissée vers le haut, x : compte (0-F), y : demi-note (0-F) 0Rxy - Note slide down, x: count (0-F), y: seminote (0-F) 0Rxy - Note glissée vers le bas, x : compte (0-F), y : demi-note (0-F) 0Txy - Transpose delay, x: count (1-7: up, 9-F: down), y: seminote (0-F) 0Txy - Délai de transposition, x : compte (1-7 : haut, 9-F : bas), y : demi-note (0-F) 0Fxx - Change speed (xx: 00-1F), change tempo (xx: 20-FF) 0Fxx - Changement de vitesse (xx : 00-1F), changement de tempo (xx : 20-FF) 0Gxx - Note delay, xx: count (00-FF) 0Gxx - Délai de note, xx : compte (00-FF) 0Sxx - Note cut, xx: count (01-FF) 0Sxx - Coupure de note, xx : compte (01-FF) 0Vxx - Master volume, xx: volume (00-3F) 0Vxx - Volume maître, xx : volume (00-3F) Mxyy - Volume delay, x: count (1-F), yy: volume (00-FF) Mxyy - Délai de volume , x : compte (1-F), yy : volume (00-FF) &Undo Ann&uler &Redo &Rétablir &Copy &Copier Cu&t Cou&per &Paste C&oller Paste Specia&l Collage spécia&l &Mix &Mélange &Overwrite &Écraser &Insert &Erase &Effacer Select &All Sélectionner &tout Patter&n &Motif &Interpolate &Interpoler &Reverse Inve&rser R&eplace Instrument R&emplacer l'instrument E&xpand É&tendre S&hrink Réd&uire &Transpose &Transposer &Decrease Note &Diminuer la note &Increase Note Aug&menter la note D&ecrease Octave Diminu&er l'octave I&ncrease Octave A&ugmenter l'octave &Change Values Fine &Decrease Values Fine &Increase Values Coarse D&ecrease Values Coarse I&ncrease Values To&ggle Track &Basculer la piste &Solo Track Piste &solo Expand E&ffect Column Shrin&k Effect Column &Unmute All Tracks Rendre a&udibles toutes les pistes QObject Error Erreur An unknown error occured. %1 Une erreur inconnue s'est produite. %1 An unknown error occured. Une erreur inconnue s'est produite. An unknown error occurred. An unknown error occurred. %1 S98ExportSettingsDialog S98 export settings Paramètres d'exportation S98 Tag Étiquette Title Titre Artist Artiste Game Jeu Year Année Genre Genre Comment Commentaire Copyright Droits d'auteur S98by S98par System Système NEC PC-9801 NEC PC-9801 Target Cible FM FM YM2608 OPNA YM2608 OPNA YM2612 OPN2 YM2612 OPN2 YM2203 OPN YM2203 OPN None SSG SSG OPN internal Interne OPN AY-3-8910 PSG PSG AY-3-8910 YM2149 PSG PSG YM2149 Support Support FM Channels Canaux FM Yes Oui Rhythm Rythme ADPCM ADPCM Resolution Résolution No Non SampleLengthDialog Resize Length SongType Standard Standard FM3ch expanded FM3canaux étendu SwapTracksDialog Swap Tracks Track 1 Track 2 ToneNoiseMacroEditor Tone Noise TransposeSongDialog Transpose Song Seminotes Exclude these instruments Reverse Clear All VgmExportSettingsDialog VGM export settings Paramètres d'exportation VGM GD3 tag Étiquette GD3 Game Jeu Name Nom English Anglais Release date Date de sortie Japanese Japonais System Système NEC PC-9801 NEC PC-9801 Track Piste Title Titre Author Auteur VGM file Fichier VGM Creator Créateur Notes Notes Target Cible FM FM YM2608 OPNA YM2608 OPNA YM2612 OPN2 YM2612 OPN2 YM2203 OPN YM2203 OPN YM2610B OPNB None SSG SSG OPN internal Interne OPN AY-3-8910 PSG PSG AY-3-8910 YM2149 PSG PSG YM2149 Support Support FM Channels Canaux FM Yes Oui Rhythm Rythme ADPCM ADPCM No Non VisualizedInstrumentMacroEditor Size: 1 Taille : 1 Size: %1 Taille : %1 Loop Boucle Loop %1 Boucle %1 Fixed Fixe Release Relâche Fix Fixe Absolute Absolu Relative Relatif WaveExportSettingsDialog WAV export settings Paramètres d'exportation WAV Loop Boucle Separate track export Reverse Check All Loop: Boucle : Sample rate Taux d'échantillonnage BambooTracker-0.6.5/BambooTracker/lang/bamboo_tracker_ja.ts000066400000000000000000005667141476276175200237310ustar00rootroot00000000000000 ADPCMSampleEditor Users: ユーザー: Memory: メモリ: Repeat 繰り返し Root key 基準キー Key キー Rate サンプルレート &Resize サイズ変更(&R) Re&verse 反転(&V) Zoom I&n 拡大(&N) Zoom &Out 縮小(&O) &Import サンプル読み込み(&I) &Clear クリア(&C) &Grid View グリッド表示(&G) Grid &Settings... グリッドの設定(&S)... &Draw Sample サンプル描画(&D) Direc&t Draw 直接描画(&T) Supported sample rate is 2kHz-55.5kHz, but the rate of selected sample is %1. サンプルレートが%1Hzのため読み込めません。2kHzから55.5kHzの間で設定してください。 The selected sample is not mono channel. 選択したサンプルはモノラルではありません。 Import sample サンプル読み込み WAV signed 16-bit PCM (*.wav) All files (*) すべてのファイル (*) AdpcmDrumkitEditor Sample assignment サンプル割り当て Key キー Pitch ピッチ Pan パン Panning パン Sample サンプル AdpcmInstrumentEditor Sample サンプル Envelope エンベロープ Users: ユーザー: Arpeggio アルペジオ Type: タイプ: Pitch ピッチ Panning パン Absolute 絶対 Fixed 固定 Relative 相対 BookmarkManagerForm Bookmark Manager ブックマークマネージャー Sort by 並び替え Position 位置順 Name 名前 Remove 削除 Clear All 全削除 Bookmark editor ブックマークエディター Order オーダー Step ステップ Create New 新規作成 Update 更新 Bookmark ブックマーク Bookmark %1 ブックマーク%1 CommentEditDialog Module Comment モジュールコメント ConfigurationDialog Configuration 設定 General 全体設定 Write only used samples 使用中のサンプルのみ書き込み Real chip interface リアルチップインターフェース Emulation core エミュレーション Reflect instrument number change インストゥルメント番号の変更を反映 Fix jamming volume ジャミング時の音量を固定 Mute hidden tracks 非表示のトラックをミュートする Restore track visibility トラックの非表示設定を復元 API MIDI input MIDI In Wave view オシロスコープ Frame rate フレームレート Wrap cursor カーソルを折り返す Wrap across orders オーダー間で折り返す Move cursor right カーソルを右に移動 Move cursor with horizontal scroll bar 横スクロールでカーソルを移動 Overwrite unused and unedited properties 未使用・未編集のプロパティを上書き Note names 音名 Notation system 表記法 Audio output オーディオ出力 The level setting for each part is valid when the mixer in the module properties is not checked. 各パートの音量レベル設定はモジュールプロパティのミキサーがチェックされていない時のみ有効になります。 Appearance 外観 Font and size フォントとサイズ Order list rows オーダーリスト行 Pattern editor rows パターンエディター行 Pattern editor header パターンエディターヘッダー Order list header オーダーリストヘッダー Colors カラースキーム Item アイテム Color Pattern editor パターンエディター Default step text デフォルトステップテキスト Default step background デフォルトステップ背景 Highlighted step 1 background ハイライトステップ1背景 Highlighted step 2 background ハイライトステップ2背景 Current step text 現在のステップテキスト Current step background 現在のステップ Current editing step background 現在編集中のステップ背景 Current cell background 現在のセル背景 Current playing step background 現在再生中のステップ背景 Selection background 選択領域背景 Hovered cell background マウスカーソルのセル背景 Default step number デフォルトステップ番号 Highlighted step 1 number ハイライトステップ1番号 Highlighted step 2 number ハイライトステップ2番号 Note text 音符テキスト Instrument text インストゥルメントテキスト Volume text 音量テキスト Effect text エフェクトテキスト Error text エラーテキスト Header text ヘッダーテキスト Header background ヘッダー背景 Mask マスク Border 境界線 Header border ヘッダー境界線 Mute ミュート状態 Unmute ミュート解除状態 Background 背景 Marker マーカー Unfocused shadow フォーカス・シャドウ Order list オーダーリスト Default row text デフォルト行テキスト Default row background デフォルト行背景 Current row text 現在の行テキスト Current row background 現在の行背景 Current editing row background 現在編集中の行背景 Current playing row background 現在再生中の行背景 Row number 行番号 Instrument list インストゥルメントリスト Default text デフォルトテキスト Selected background 選択背景 Hovered background マウスカーソル背景 Selected hovered background マウスカーソル選択背景 Oscilloscope オシロスコープ Foreground 前景 Save 保存 Load 読み込み Formats フォーマット Keys キーボード Shortcuts ショートカット Action 動作 Note entry layout 音符入力レイアウト Custom カスタム Low 低音 High 高音 Key off キーオフ Octave up オクターブアップ Octave down オクターブダウン Echo buffer エコーバッファ Edit settings エディット設定 Page jump length Page jump length: ページジャンプ長 General settings 総合設定 Show row numbers in hex 行番号を16進数で表示 Preview previous/next orders 前後のオーダーをプレビュー Backup modules モジュールのバックアップ Don't select on double click ダブルクリックで選択しない Reverse FM volume order FMの音量の逆順にする Retrieve channel state チャンネル状態を復元 Enable translation 翻訳を有効 Show FM detune as signed FMデチューンを符号付きで表記 Fill 00 to effect value エフェクト値に00を自動で設定 Description: 説明: Sound サウンド Sample rate サンプルレート Buffer length バッファ長 1ms Device デバイス FM envelope text FMエンベロープテキスト Add 追加 Edit 編集 Remove 削除 Mixer ミキサー Part パート Reset リセット Virtual port 仮想ポート Master マスター Wrap the cursor around the edges of the pattern editor. パターンの両端でカーソルを折り返します。 Move to the previous or next order when reaching the top or bottom in the pattern editor. パターンの上下の端から前後のパターンへ移動します。 Preview the previous and next orders in the pattern editor. パターンエディターで前後のオーダーをプレビューします。 Automatically move the cursor right after entering effects in the pattern editor. パターンエディターでエフェクト入力後にカーソルを右の列へ移動します。 Automatically fill 00 to the effect value column upon entering an effect ID. エフェクトIDを入力時にエフェクト値を自動で00に設定します。 Move the cursor with the horizontal scroll bar in the order list and the pattern editor. オーダーリストとパターンエディターにおいて横スクロールバーでカーソルをセル単位で移動させます。 Overwrite unused and unedited instrument properties when creating new properties. If disabled, override unused properties regardless of editing. 新しいインストゥルメントプロパティを作成する際に未使用かつ未編集の既存のプロパティを上書きします。編集したかどうかに関わらず上書きする場合はチェックを外してください。 Only send samples used by instruments to the ADPCM memory. It is recommended to turn this off if you change ADPCM samples frequently due to the high cost of rewriting.. インストゥルメントで使用されているADPCMサンプルのみをADPCMメモリへ書き込みます。メモリの書きかえには高コストであるため、頻繁にサンプルを変更する場合には無効にすることをお勧めします。 Automatically update the instrument number in patterns when an instrument's number is changed. インストゥルメントの番号が変更されたとき、パターン中の対応するインストゥルメント番号を同期させます。 Automatically mute tracks when they are hidden. トラックの可視性が変更されたとき、非表示のトラックをミュートします。 Transpose down one semitone トランスポーズ (半音下げる) Transpose up one semitone トランスポーズ (半音上げる) Transpose down one octave トランスポーズ (1オクターブ下げる) Transpose up one octave トランスポーズ (1オクターブ上げる) The change of emulator will be effective after restarting the program. エミュレータのの変更は次回起動以降に反映されます。 Display row numbers and the playback position on the status bar in hexadecimal. オーダー番号とステップ番号を16進数で表示します。 Create a backup copy of the existing file when saving a module. モジュール保存時に既存のデータをコピーしてバックアップを作成します。 Don't select the whole track when double-clicking in the pattern editor. パターンエディターでのダブルクリック時にトラック全体を選択しないようにします。 Reverse the order of FM volume so that 00 is the quietest in the pattern editor. Reverse the order of FM volume so that 00 is the quietest in the pattern editor パターンエディターでFMの音量を00が最小になるよう順序を変更します。 Reconstruct the current channel's state from previous orders upon playing. 再生時に以前のオーダーから現在のチャンネルの状態を再構築します。 Translate to your language from the next launch. See readme to check supported languages. テキストが次回の起動時からあなたの言語に翻訳されます。サポートされている言語についてはreadmeを参照してください。 Display FM detune values as signed numbers in the FM envelope editor. FMエンベロープエディタでFMのデチューン値を符号付きの値として表示します。 Set maximum volume during jam mode. When unchecked, the volume is changed by the volume spinbox. ジャムモード中のインストゥルメントの音量を最大に固定します。無効時にはツールバーの音量設定によって音量が変更されます。 Restore the previous track visibility on startup. 起動時に前回のプログラム終了時のトラックの可視状態を復元します。 Play and stop 再生/停止 Play 再生 Play from start 最初から再生 Play pattern パターンを再生 Play from cursor カーソル位置から再生 Play from marker マーカー位置から再生 Play step ステップ再生 Stop 停止 Focus on pattern editor パターンエディターへ移動 Focus on order list オーダーリストへ移動 Focus on instrument list インストゥルメントリストへ移動 Toggle edit/jam mode Edit/Jamモード切替え Set marker マーカーを設定 Paste and mix ミックス貼り付け Paste and overwrite 上書き貼り付け Paste and insert 挿入貼り付け Select all 全選択 Deselect 選択解除 Select row 行を選択 Select column 列を選択 Select pattern パターンを選択 Select order オーダーを選択 Go to step ステップ移動 Toggle track トラックをミュート Solo track トラックをソロ状態に変更 Interpolate 補間 Reverse 反転 Go to previous order 前のオーダーへ移動 Go to next order 次のオーダーへ移動 Toggle bookmark ブックマーク設定 Previous bookmark 前のブックマークへ移動 Next bookmark 次のブックマークへ移動 Previous instrument 前のインストゥルメントを選択 Next instrument 次のインストゥルメントを選択 Mask instrument インストゥルメント入力をマスク Mask volume 音量入力をマスク Edit instrument インストゥルメントを編集 Follow mode フォローモード Duplicate order オーダーを複製 Clone patterns パターンをクローン Clone order オーダーをクローン Replace instrument インストゥルメント置換 Expand pattern パターン拡大 Shrink pattern パターン縮小 Fine decrease values 値を1だけ減少 Fine increase values 値を1だけ増加 Coarse decrease values 値を16だけ減少 Coarse increase valuse 値を16だけ増加 Expand effect column エフェクト列の拡張 Shrink effect column エフェクト列の縮小 Previous highlighted step 前のハイライトステップへ移動 Next highlighted step 次のハイライトステップへ移動 Increase pattern size パターン長を増加 Decrease pattern size パターン長を縮小 Increase edit step エディットステップを増加 Decrease edit step エディットステップを減少 Display effect list エフェクト一覧を表示 Previous song 前のソングに切り替え Next song 次のソングに切り替え Jam volume up ジャム音量を上げる Jam volume down ジャム音量を下げる None なし Description: %1 説明: %1 Set %1 セット%1 Open color scheme カラースキームの読み込み ini file (*.ini) All files (*) すべてのファイル (*) Error エラー An unknown error occurred while loading the color scheme. カラースキームの読み込み中に不明なエラーが発生しました。 Save color scheme カラースキームの保存 Failed to save the color scheme. カラースキームの保存に失敗しました。 EffectDescription Arpeggio, x: 2nd note (0-F), y: 3rd note (0-F) アルペジオ, x: 第2音(0-F), y: 第3音(0-F) Portamento up, xx: depth (00-FF) ポルタメント・アップ, xx: デプス(00-FF) Portamento down, xx: depth (00-FF) ポルタメント・ダウン, xx: デプス(00-FF) Tone portamento, xx: depth (00-FF) トーン・ポルタメント, xx: デプス(00-FF) Vibrato, x: period (0-F), y: depth (0-F) ビブラート, x: ピリオド(0-F), y: デプス(0-F) Tremolo, x: period (0-F), y: depth (0-F) トレモロ, x: ピリオド(0-F), y: デプス(0-F) Pan, xx: 00 = no sound, 01 = right, 02 = left, 03 = center パン, xx: 00 = 無音, 01 = 右, 02 = 左, 03 = 中央 Volume slide, x: up (0-F), y: down (0-F) ボリューム・スライド, x: アップ(0-F), y: ダウン(0-F) Jump to beginning of order xx オーダーxxの先頭へジャンプ End of song ソングの停止 Jump to step xx of next order 次のオーダーのステップxxへジャンプ Change speed (xx: 00-1F), change tempo (xx: 20-FF) スピード変更(xx: 00-1F), テンポ変更(xx: 20-FF) Note delay, xx: count (00-FF) ノート・ディレイ, xx: カウント(00-FF) Auto envelope, x: shift amount (0-F), y: shape (0-F) オートエンベロープ, x: シフト量(x-8), y: エンベロープ形状(0-F) Hardware envelope period 1, xx: high byte (00-FF) ハードウェアエンベロープ周期1, xx: 上位バイト(00-FF) Hardware envelope period 2, xx: low byte (00-FF) ハードウェアエンベロープ周期2, xx: 下位バイト(00-FF) Retrigger, x: volume slide (0-7: up, 8-F: down), y: tick (1-F) リトリガー, x: ボリュームスライド(0-7: アップ, 8-F: ダウン), y: ティック(1-F) Set groove xx グルーブをxxに設定 Detune, xx: pitch (00-FF) デチューン, xx: ピッチ(00-FF) Note slide up, x: count (0-F), y: seminote (0-F) ノート・スライドアップ, x: カウント(0-F), y: 半音数(0-F) Note slide down, x: count (0-F), y: seminote (0-F) ノート・スライドダウン, x: カウント(0-F), y: 半音数(0-F) Note cut, xx: count (00-FF) ノート・カット, xx: カウント(00-FF) Transpose delay, x: count (0-7: up, 8-F: down), y: seminote (0-F) トランスポーズ・ディレイ, x: カウント(0-7: アップ, 8-F: ダウン), y: 半音数(0-F) Extended volume slide, x: up (0-F), y: down (0-F) 拡張ボリューム・スライド, x: アップ(0-F), y: ダウン(0-F) Volume delay, x: count (0-F), yy: volume (00-FF) ボリューム・ディレイ, x: カウント(0-F), yy: 音量(00-FF) Tone/Noise mix, xx: 00 = no sound, 01 = tone, 02 = noise, 03 = tone & noise トーン/ノイズ・ミックス, xx: 00=消音, 01=トーン, 02=ノイズ, 03=トーン & ノイズ Master volume, xx: volume (00-3F) マスターボリューム, xx: 音量(00-3F) Noise pitch, xx: pitch (00-1F) ノイズ・ピッチ, xx: ピッチ(00-1F) Register address bank 0, xx: address (00-6B) レジスタ番地指定 バンク0, xx: 番地(00-B6) Register address bank 1, xx: address (00-6B) レジスタ番地指定 バンク1, xx: 番地(00-B6) Register value set, xx: value (00-FF) レジスタ値指定, xx: レジスタ値(00-FF) AR control, x: operator (1-4), yy: attack rate (00-1F) ARコントロール, x: オペレーター(1-4), yy: AR値(00-1F) Brightness, xx: relative value (01-FF) ブライトネス, xx: 相対値(01-FF) DR control, x: operator (1-4), yy: decay rate (00-1F) DRコントロール, x: オペレーター(1-4), yy: DR値(00-1F) Envelope reset, xx: count (00-FF) エンベロープ・リセット, xx: カウント(00-FF) FB control, xx: feedback value (00-07) FBコントロール, xx: フィードバック値(00-07) Fine detune, xx: pitch (00-FF) ファイン・デチューン, xx: ピッチ(00-FF) ML control, x: operator (1-4), y: multiple (0-F) MLコントロール, x: オペレーター(1-4), y: ML値(0-F) RR control, x: operator (1-4), y: release rate (0-F) RRコントロール, x: オペレーター(1-4), y: RR値(0-F) TL control, x: operator (1-4), yy: total level (00-7F) TLコントロール, x: オペレーター(1-4), yy: TL値(00-7F) Invalid effect 無効なエフェクト EffectListDialog Effect list エフェクト一覧 Effect エフェクト Track type トラックタイプ Description 説明 Rhythm リズム FMEnvelopeSetEditDialog Edit FM envelope set FMエンベロープセットの編集 Add 追加 Set digit types in the order of appearance. 数字に対応するパラメーターを出現順に設定します。 Remove 削除 Number 番号 Type タイプ Skip スキップ FMOperatorTable SSGEG Type タイプ Operator %1 オペレーター%1 Copy envelope エンベロープをコピー Paste envelope エンベロープを張り付け Paste envelope From エンベロープを形式貼り付け Copy operator オペレーターをコピー Paste operator オペレーターを張り付け FileIOErrorMessageBox Path does not exist. 指定されたパスが存在しません。 Unsupported file format. 非対応のファイルフォーマットです。 Could not load the %1 properly. Please make sure that you have the latest version of BambooTracker. %1を読み込めませんでした。最新版のBambooTrackerで読み込みを行なってください。 Could not load the %1. It may be corrupted. Stopped at %2. %1を読み込めませんでした。ファイルが破損している可能性があります。停止ポイント: %2。 Failed to load %1. %1の読み込みに失敗しました。 Failed to export to %1. %1の書き出しに失敗しました。 Failed to save the %1. %1の保存に失敗しました。 Error エラー Could not open the file. ファイルを開けません。 FileType module モジュール s98 S98 vgm VGM wav WAV bank バンク instrument インストゥルメント FmInstrumentEditor Envelope エンベロープ Users: ユーザー: LFO/Operator sequence LFO/オペレーターシーケンス Operator sequence オペレーターシーケンス Operator: オペレーター: Sequence シーケンス LFO Start count: 開始カウント: AM operators AMオペレーター Operator 1 オペレーター1 Operator 2 オペレーター2 Operator 3 オペレーター3 Operator 4 オペレーター4 Envelope reset エンベロープリセット Reset envelope before key on キーオン前にエンベロープを初期化 FM 3ch FM 3ch Arpeggio/Pitch アルペジオ/ピッチ Arpeggio アルペジオ Type: タイプ: Pitch ピッチ Panning パン Freq All Absolute 絶対 Fixed 固定 Relative 相対 Error エラー Did not match the clipboard text format with %1. クリップボードのテキストの形式が%1と一致しません。 Copy envelope エンベロープをコピー Paste envelope エンベロープを張り付け Paste envelope From エンベロープを形式貼り付け Copy LFO parameters LFOパラメーターをコピー Paste LFO parameters LFOパラメーターを張り付け GoToDialog Go To カーソル移動 Order オーダー Step ステップ Track トラック GridSettingsDialog Grid Settings グリッドの設定 Interval 間隔 GrooveSettingsDialog Groove Settings グルーブ設定 Remove 削除 Add 追加 Sequence シーケンス Copy Fxx Fxxでコピー Expand 拡大 Generate 生成 Shrink 縮小 Pad パッド Speed: %1 スピード: %1 HideTracksDialog Hide Tracks トラックの表示/非表示 Check All 全選択 Reverse 反転 Set the visibility of tracks: 表示するトラックを設定: InstrumentSelectionDialog Select instruments インストゥルメント選択 Search... 検索... KeySignatureManagerForm Key Signature Manager 調号マネージャー Key signature editor 調号エディター Key 調 Order オーダー Step ステップ Create New 新規作成 Update 更新 Remove 削除 Clear All 全削除 KeyboardShortcutListDialog Keyboard shortcuts キーボードショートカット一覧 qrc:/doc/shortcuts MainWindow BambooTracker Order List オーダーリスト Module Settings モジュール設定 Copyright 著作権 Author 作曲者 Edit settings エディット設定 Song Settings ソング設定 Speed スピード Tempo テンポ Untitled 無題 Groove グルーブ Instruments インストゥルメント &File ファイル(&F) &Export エクスポート(&E) &Edit 編集(&E) &Select 選択(&S) Paste Specia&l 特殊貼り付け(&I) &Pattern パターン(&P) &Transpose トランスポーズ(&T) &Bookmarks ブックマーク(&B) &Change Values 値一括変更(&C) S&ong ソング(&O) &Module モジュール(&M) Clean&up 最適化(&U) &Instrument インストゥルメント(&I) &Help ヘルプ(&H) &New... 新規作成(&N)... Ctrl+N &Open... 開く(&O)... Ctrl+O &Save 保存(&S) Ctrl+S Save &As... 名前を付けて保存(&A)... E&xit 終了(&X) &Undo 元に戻す(&U) Ctrl+Z &Redo やり直し(&R) Ctrl+Y Cu&t 切り取り(&T) Ctrl+X &Copy コピー(&C) Ctrl+C &Paste 貼り付け(&P) Ctrl+V Ctrl+M &Delete 削除(&D) Pattern size パターン長 Step ステップ Key repetition キー入力繰り返し Title タイトル Songs ソング &Recent Files 最近使ったファイル(&R) Patter&n パターン(&N) &Tracker トラッカー(&T) Vie&w ビュー(&W) Main toolbar メインツールバー Secondary toolbar サブツールバー Del &All 全選択(&A) Ctrl+A &None 選択解除(&N) Esc E&xpand 拡大(&X) S&hrink 縮小(&H) &Decrease Note 半音を下げる(&D) Ctrl+F1 &Increase Note 半音上げる(&I) Ctrl+F2 D&ecrease Octave 1オクターブ下げる(&E) Ctrl+F3 I&ncrease Octave 1オクターブ上げる(&N) Ctrl+F4 &Insert Order オーダーを挿入(&I) &Remove Order オーダーを削除(&R) &Module Properties... モジュール情報(&M)... Ctrl+P &New Instrument 新規インストゥルメント(&N) &Remove Instrument インストゥルメント削除(&R) &Clone Instrument インストゥルメントをクローン(&C) &Deep Clone Instrument インストゥルメントをディープクローン(&D) &Load From File... ファイルから読み込み(&L)... &Save To File... ファイルへ書き出し(&S)... &Edit... 編集(&E)... Ctrl+I &Play 再生(&P) Play P&attern パターンの初めから再生(&A) F6 Play &From Start 最初から再生(&F) F5 Play From C&ursor カーソル位置から再生(&U) F7 &Stop 停止(&S) F8 &Edit Mode 編集モード(&E) Space To&ggle Track トラックを演奏(&G) Alt+F9 S&olo Track トラックをソロ演奏(&O) Alt+F10 &Kill Sound サウンド初期化(&K) F12 &About... バージョン情報(&A)... Fo&llow Mode フォローモード(&L) ScrollLock &Groove Settings... グルーブ設定(&G)... &Configuration... 設定(&C)... &Duplicate Order オーダーを複製(&D) Ctrl+D Move Order &Up オーダーを上に移動(&U) Move Order Do&wn オーダーを下に移動(&W) &Clone Patterns パターンをクローン(&C) Alt+D Clone &Order オーダーをクローン(&O) &Comments... コメント(&C)... &Interpolate 補完(&P) Ctrl+G &Reverse 反転(&R) Ctrl+R R&eplace Instrument インストゥルメント置換(&E) Alt+S &Row 行(&R) &Column 列(&C) &Order オーダー(&O) Remove Unused &Instruments 未使用のインストゥルメントを削除(&I) Remove Unused &Patterns 未使用のパターンを削除(&P) &WAV... &VGM... &Overwrite Paste Overwrite 上書き(&O) &Clear クリア(&C) &Effect List... エフェクト一覧(&E)... Effect List エフェクト一覧 F1 &Shortcuts... ショートカット一覧(&S)... E&xport To Bank File... バンクファイルへ書き出し(&X)... Remove &Duplicate Instruments 重複したインストゥルメントの削除(&D) Re&name Instrument 名前を編集(&N) &Bookmark Manager... ブックマークマネージャー(&B)... Fine &Decrease Values 1だけ減少(&D) Shift+F1 Fine &Increase Values 1だけ増加(&I) Shift+F2 Coarse D&ecrease Values 16だけ減少(&E) Shift+F3 Coarse I&ncrease Values 16だけ増加(&N) Shift+F4 &Toggle Bookmark ブックマーク登録(&T) Ctrl+K &Next Bookmark 次のブックマーク(&N) Ctrl+PgDown &Previous Bookmark 前のブックマーク(&P) Ctrl+PgUp &Instrument Mask インストゥルメントマスク(&I) &Volume Mask 音量マスク(&V) Set Ro&w Marker マーカーを設定(&W) Ctrl+B Play From &Marker マーカー位置から再生(&M) Ctrl+F7 &Go To... カーソル移動(&G)... Alt+G Remove Unused &ADPCM Samples 未使用のADPCMサンプルを削除(&A) &Status Bar ステータスバー(&S) &Toolbar ツールバー(&T) New Drumki&t 新規ドラムキット(&T) &Wave View オシロスコープ(&W) &Transpose Song... ソングをトランスポーズ(&T)... &Swap Tracks... トラック入れ替え(&S)... &Insert 挿入(&I) &Cursor カーソル(&C) &Selection 選択(&S) &Fill 敷き詰め(&F) &Hide Tracks... トラックの表示/非表示(&H)... &Estimate Song Length... ソングの長さを算出(&E)... &Key Signature Manager... 調号マネージャー(&K)... &Welcome... ようこそ(&W)... &Mix ミックス(&M) &Import From Bank File... バンクファイルから取り込み(&I)... &S98... Octave オクターブ Octave: %1 Octave: オクターブ: %1 Error エラー Instrument %1 Instrument インストゥルメント%1 Welcome to BambooTracker v%1! BambooTracker v%1へようこそ! Open instrument インストゥルメントを開く Save instrument インストゥルメント保存 Open bank バンクを開く Select instruments to load: 読み込むインストゥルメントを選択: No instrument インストゥルメントなし Standard 標準 English Default notation system Set the name of suitable notation system (English or German) English Volume 音量 Step highlight 1st ステップハイライト 1st 2nd Welcome to BambooTracker! BambooTrackerへようこそ! Don't know where to start? はじめに Check the demo modules and instruments included with your download of BambooTracker. BambooTrackerのダウンロードフォルダに含まれるデモモジュールやインストゥルメントをチェックしてみてください。 Need a list of effects and shortcuts? エフェクトやショートカットのリストは? Check the Help menu at the top of the window. ウィンドウの上部にある「ヘルプ」メニューをチェックしてください。 Still lost? まだわかんない... The README.md has a link to our Discord server. README.mdに、Discordサーバーへのリンクがあります。 Think you've found a bug? Missing a feature? バグ見つけた!あんな機能ほしい! BambooTracker is still in development, bugs and missing features are to be expected. So we need your help! BambooTrackerは開発中のソフトウェアなので、バグがあったりや十分な機能がなかったりします。そのため、皆さんのご協力が必要です。 Please report any bugs you find and requests and features you'd like to see on our Discord server or our bug tracker (%1). バグや要望などは、Discordサーバーやバグトラッカー(%1)でご報告をお願いします。 If you're a developer yourself or would like to start being one, consider contributing to the project yourself. Any help would be appreciated! また開発者として直接プロジェクトに参加することも歓迎しています! Welcome ようこそ The number of instruments has reached the upper limit. インストゥルメント数が上限に達しています。 Select instruments to save: 保存するインストゥルメントを選択: Save bank バンク保存 - Custom カスタム PC-9821 with PC-9801-86 PC-9821 with Speak Board PC-88VA2 NEC PC-8801mkIISR FM3ch expanded FM3ch拡張 Insufficient memory size to load ADPCM samples. Please delete the unused samples. ADPCMサンプルを読み込むためのメモリが足りません。未使用のサンプルを削除してください。 The module has been changed. Do you want to save it? モジュールが変更されています。保存しますか? Could not initialize MIDI input. MIDI Inを初期化できませんでした。 BambooTracker module (*.btm) All files (*) すべてのファイル (*) WAV signed 16-bit PCM (*.wav) Export %1 to WAV %1をWAVへ書き出し VGM file (*.vgm) S98 file (*.s98) Do you want to remove all duplicate instruments? 重複したインストゥルメントを削除しますか? Do you want to remove all unused ADPCM samples? 未使用のADPCMサンプルを削除しますか? Do you want to transpose a song? ソングをトランスポーズしますか? Do you want to swap tracks? トラックを入れ替えますか? Approximate song length: %1m%2s ソングの長さ: %1分%2秒 Instrument: %1 Instrument: インストゥルメント: %1 Do you want to change song properties? ソング情報を変更しますか? Change to jam mode ジャムモードに切り替え Change to edit mode 編集モードに切り替え YM2608 Music Tracker Web: This software is licensed under the GNU General Public License v2.0 or later. 本ソフトウェアはGNU General Public License v2.0以降でライセンスされています。 Source is available at: ソースコードは以下のサイトで入手可能です。 Libraries: 使用ライブラリ: Also see changelog which lists contributors. このプロジェクトのコントリビューターについてはChangelogもご覧ください。 Thank you to everyone who reports bugs, makes suggestions, and contributes to this project! バグ報告や機能提案など、このプロジェクトに貢献してくださる皆様に感謝いたします。 C86CTL by (C) honet (BSD 3-Clause) emu2149 by (C) Mitsutaka Okazaki (MIT License) fmopn by (C) Tatsuyuki Satoh, Jarek Burczynski, ValleyBell (GPL v2+) libOPNMIDI by (C) Vitaly Novichkov (MIT License part) Nuked OPN-MOD by (C) Alexey Khokholov (Nuke.YKT), Jean Pierre Cimalando (LGPL v2.1+) Qt (GPL v2+ or LGPL v3) RtAudio by (C) Gary P. Scavone (RtAudio License) RtMidi by (C) Gary P. Scavone (RtMidi License) SCCI by (C) gasshi (SCCI License) Silk icons by (C) Mark James (CC BY 2.5 or 3.0) ymdeltat by (C) Tatsuyuki Satoh, Jarek Burczynski, ValleyBell (GPL v2+) About バージョン情報 Failed to backup module. モジュールのバックアップ作成に失敗しました。 Save module モジュール保存 Open module モジュールを開く Do you want to remove all unused instruments? 未使用のインストゥルメントを削除しますか? Do you want to remove all unused patterns? 未使用のパターンを削除しますか? Export to WAV WAV書き出し Cancel キャンセル Export to VGM VGM書き出し Export to S98 S98書き出し Warning 注意 %1 If you execute this command, the command history is reset. %1この操作を実行すると、すべてのコマンド履歴は消去されます。 Could not open the audio stream. Please change the sound settings in Configuration. オーディオストリームを開けませんでした。環境設定でサウンド設定を変更してください。 Could not set the sample rate of the audio stream to %1Hz. Currently the stream runs on %2Hz instead. オーディオ出力のサンプルレートを%1Hzに設定できませんでした。一時的に%2Hzで出力します。 ModulePropertiesDialog Module properties モジュール情報 Tick frequency ティック周波数 60Hz (NTSC) 50Hz (PAL) Custom カスタム Mixer ミキサー PC-9821 with PC-9801-86 PC-9821 with Speak Board PC-88VA2 PC-8801mkIISR Custom mixer カスタムミキサー FM SSG Set 設定 Song control ソング設定 Song ソング Update 更新 Remove 削除 Insert 追加 Untitled 無題 Number 番号 Title タイトル Song type ソングタイプ ModuleSaveCheckDialog Save changes to %1? %1の変更を保存しますか? Untitled 無題 NotationSystem English 英語 German ドイツ語 OrderListPanel &Insert Order オーダーを挿入(&I) &Remove Order オーダーを削除(&R) &Duplicate Order オーダーを複製(&D) &Clone Patterns パターンをクローン(&C) Clone &Order オーダーをクローン(&O) Move Order &Up オーダーを上に移動(&U) Move Order Do&wn Move Order Dow&n オーダーを下に移動(&W) Cop&y コピー(&Y) &Paste 貼り付け(&P) PanMacroEditor Right Left Panning Left Center 中央 Right PatternEditorPanel &Undo 元に戻す(&U) &Redo やり直し(&R) &Copy コピー(&C) Cu&t 切り取り(&T) &Paste 貼り付け(&P) Paste Specia&l 特殊貼り付け(&I) &Mix ミックス(&M) &Overwrite 上書き(&O) &Insert 挿入(&I) &Erase 削除(&E) Select &All 全選択(&A) Patter&n パターン(&N) &Interpolate 補完(&P) &Reverse 反転(&R) R&eplace Instrument インストゥルメント置換(&E) E&xpand 拡大(&X) S&hrink 縮小(&H) &Transpose トランスポーズ(&T) &Decrease Note 半音を下げる(&D) &Increase Note 半音上げる(&I) D&ecrease Octave 1オクターブ下げる(&E) I&ncrease Octave 1オクターブ上げる(&N) &Change Values 値一括変更(&C) Fine &Decrease Values 1だけ減少(&D) Fine &Increase Values 1だけ増加(&I) Coarse D&ecrease Values 16だけ減少(&E) Coarse I&ncrease Values 16だけ増加(&N) To&ggle Track トラックを演奏(&G) &Solo Track トラックをソロ演奏(&S) Expand E&ffect Column エフェクト列の拡張(&F) Shrin&k Effect Column エフェクト列の縮小(&K) &Unmute All Tracks 全トラックのミュート解除(&U) QObject Error エラー An unknown error occurred. 不明なエラーが発生しました。 An unknown error occurred. %1 不明なエラーが発生しました。 %1 S98ExportSettingsDialog S98 export settings S98エクスポート設定 Tag タグ Title Title: タイトル Artist Artist: 作曲者 Game Game: ゲーム Year Year: Genre Genre: ジャンル Comment コメント Copyright Copyright: 著作権 S98by S98by: S98作成者 System System: システム NEC PC-9801 Target 出力先音源 FM YM2608 OPNA YM2612 OPN2 YM2203 OPN None なし SSG OPN internal OPN内蔵 AY-3-8910 PSG YM2149 PSG Support 対応リスト FM Channels FMチャンネル数 Yes Rhythm リズム ADPCM Resolution 解像度 No SampleLengthDialog Resize ADPCMのサイズ変更 Length 長さ SongType Standard 標準 FM3ch expanded FM3ch拡張 SsgInstrumentEditor Waveform 波形 Users: ユーザー: Square mask 矩形波マスク Raw レジスタ値 Tone/Mask ratio トーン/マスク比 Tone/Noise トーン/ノイズ Envelope エンベロープ Hardware envelope frequency ハードウェアエンベロープ周波数 Tone/Env ratio トーン/ハード比 Arpeggio アルペジオ Type: タイプ: Pitch ピッチ Sq Tri Saw InvSaw SMTri SMSaw SMInvSaw HEnv %1 Absolute 絶対 Fixed 固定 Relative 相対 SwapTracksDialog Swap Tracks トラック入れ替え Track 1 トラック1 Track 2 トラック2 ToneNoiseMacroEditor Tone トーン Noise ノイズ TransposeSongDialog Transpose Song ソングをトランスポーズ Seminotes 半音単位の移動量 Exclude these instruments 指定したインストゥルメントを除外 Reverse 反転 Clear All 全解除 VgmExportSettingsDialog VGM export settings VGMエクスポート設定 GD3 tag GD3タグ Game ゲーム Name Name: ゲーム名 English Release date Release date: 発売日 Japanese 日本語 System System: システム NEC PC-9801 Track トラック Title Title: タイトル Author Author: 作曲者 VGM file VGMファイル Creator Creator: 作成者 Notes Notes: その他 Target 出力先音源 FM YM2608 OPNA YM2612 OPN2 YM2203 OPN YM2610B OPNB None なし SSG OPN internal OPN内蔵 AY-3-8910 PSG YM2149 PSG Support 対応リスト FM Channels FMチャンネル数 Yes Rhythm リズム ADPCM No VisualizedInstrumentMacroEditor Size: 1 長さ: 1 Size: %1 長さ: %1 Loop ループ Loop %1 Loop ループ%1 Fixed 固定 Release リリース Absolute 絶対 Relative 相対 WaveExportSettingsDialog WAV export settings WAVエクスポート設定 Loop ループ Sample rate サンプルレート Separate track export トラック別にエクスポート Reverse 反転 Check All 全選択 BambooTracker-0.6.5/BambooTracker/lang/bamboo_tracker_pl.ts000066400000000000000000006311511476276175200237360ustar00rootroot00000000000000 ADPCMSampleEditor Users: Używane przez: Memory: Pamięć: Repeat Powtarzaj Root key Klawisz podstawowy Key Klawisz C C C# C# D D D# D# E E F F F# F# G G G# G# A A A# A# B B Rate Częstotliwość próbkowania &Resize Zmień rozmiar(&R) Re&verse Odwróć(&V) Zoom I&n Przybliż(&N) Zoom &Out Oddal(&O) &Import Importuj(&I) &Clear Wyczyść(&C) &Grid View Widok siatki(&G) Grid &Settings... Ustawienia siatki(&S)... &Draw Sample Rysuj próbkę(&D) Direc&t Draw Rysuj bezpośrednio(&T) Supported sample rate is 2kHz-55.5kHz, but the rate of selected sample is %1. Wspierana częstotiwość próbkowania to 2kHz-55.5kHz, podczas gdy częstotliwość tej próbki to %1. The selected sample is not mono channel. Wybrana próbka nie jest mono. Import sample Importuj próbkę WAV signed 16-bit PCM (*.wav) All files (*) Wszystkie pliki(*) BookmarkManagerForm Bookmark Manager Menedżer zakładek Sort by Sortuj według Position Pozycja Name Nazwa Remove Usuń Clear All Wyczyść wszystko Bookmark editor Edytor zakładek Order Klatka Step Krok Create New Utwórz nowy Update Odśwież Bookmark Zakładka Bookmark %1 Zakładka %1 CommentEditDialog Module Comment Komentarz do modułu ConfigurationDialog Configuration Konfiguracja General Ogólne Move cursor by horizontal scroll Przenoś kursor przy pomocy poziomego paska przewijania Overwrite unused&unedited property Nadpisz nieużywane i niezedytowane własności Write only used samples Zapisuj tylko używane próbki Real chip interface Interfejs prawdziwego układu Emulation core Emulator układu Reflect instrument number change Wprowadzaj zmiany w numerach instrumentów Fix jamming volume Ustaw stałą głośnosć trybu improwizacji Mute hidden tracks Wycisz ukryte kanały Restore track visibility Przywróc widoczność kanałów API MIDI input MIDI In Wave view Podgląd fali Frame rate Framerate Wrap cursor Wrap across orders Move cursor right Move cursor with horizontal scroll bar Overwrite unused and unedited properties Note names Nazwy nut Notation system System nazewnictwa Audio output Wyjście audio The level setting for each part is valid when the mixer in the module properties is not checked. Ustawienie poziomu głośności dla każdej części składowej będzie uwzględniane dopero gdy pozycja "mikser" we właściwościach modułu jest odznaczona. Appearance Wygląd Font and size Czcionka i wielkość Order list rows Wiersze listy klatek Pattern editor rows Wiersze edytora wzorców Pattern editor header Nagłówek edytora wzorców Order list header Nagłówek listy klatek Colors Kolory Item Atrybuty Color Kolor Pattern editor Edytor wzorca Default step text Domyślny tekst kroku Default step background Domyślne tło kroku Highlighted step 1 background Tło podświetlonego kroku 1 Highlighted step 2 background Tło podświetlonego kroku 2 Current step text Tekst obecnego kroku Current step background Tło obecnego kroku Current editing step background Tło obecnie edytowanego kroku Current cell background Tło obecnej komórki Current playing step background Tło obecnie odtwarzanego kroku Selection background Tło wyboru Hovered cell background Tło najechanej komórki Default step number Domyślny nuer kroku Highlighted step 1 number Numer podkreślonego kroku 1 Highlighted step 2 number Numer podkreślonego kroku 2 Note text Tekst nuty Instrument text Tekst instrumentu Volume text Tekst głośności Effect text Tekst efektu Error text Tekst błędu Header text Tekst nagłówka Header background Tło nagłówka Mask Maska Border Obramowanie Header border Obramowanie nagłówka Mute Wycisz Unmute Wyłącz wyciszenie Background Tło Marker Znacznik Unfocused shadow Nieskupiony cień Order list Lista klatek Default row text Domyślny tekst wiersza Default row background Domyślne tło wiersza Current row text Tekst obecnego wiersza Current row background Tło obecnego wiersza Current editing row background Tło obecnie edytowanego wiersza Current playing row background Tło obecnie odtwarzanego wiesza Row number Numer wiersza Instrument list Lista instrumentów Default text Domyślny tekst Selected background Wybrane tło Hovered background Tło najechania Selected hovered background Tło zaznaczonego najechania Oscilloscope Oscyloskop Foreground Pierwszy plan Save Zapisz Load Wczytaj Formats Formaty Keys Klawisze Shortcuts Skróty Action Akcja Note entry layout Układ dodawania nut Custom Niestandardowy Low Niskie C C C# C# D D D# D# E E F F F# F# G G G# G# A A A# A# B B High Wysokie Key off Puszczony klawisz Octave up Oktawa w górę Octave down Oktawa w dół Echo buffer Bufor echa Edit settings Edytuj ustawienia Page jump length Page jump length: Długość przeskoku stron General settings Ogólne ustawienia Warp cursor Przenieś kursor Warp across orders Przeskakuj przez klatki Show row numbers in hex Pokazuj numery wierszy w systemie szesnastkowym Preview previous/next orders Pokaż podgląd na poprzednią/następną klatkę Backup modules Twórz kopie zapasowe modułów Don't select on double click Nie wybieraj przy pomocy podwójnego kliknięcia Reverse FM volume order Odwróc kolejność poziomów głośności FM Move cursor to right Przesuń kursor w prawo Retrieve channel state Przywróć stan kanału Enable translation Włącz tłumaczenie Show FM detune as signed Pokazuj parametr rozstroju FM ze znakiem Fill 00 to effect value Dodawaj 00 do wartości efektów Description: Opis: Sound Dźwięk Sample rate Częstotliwość próbkowania Buffer length Długość bufora 1ms Device Urządzenie FM envelope text Tekst obwiedni FM Add Dodaj Edit Edytuj Remove Usuń Mixer Mikser Part Częśćiowe Reset Resetuj Virtual port Wirtualny port Master Główny Wrap the cursor around the edges of the pattern editor. Move to the previous or next order when reaching the top or bottom in the pattern editor. Preview the previous and next orders in the pattern editor. Automatically move the cursor right after entering effects in the pattern editor. Automatically fill 00 to the effect value column upon entering an effect ID. Move the cursor with the horizontal scroll bar in the order list and the pattern editor. Overwrite unused and unedited instrument properties when creating new properties. If disabled, override unused properties regardless of editing. Only send samples used by instruments to the ADPCM memory. It is recommended to turn this off if you change ADPCM samples frequently due to the high cost of rewriting.. Automatically update the instrument number in patterns when an instrument's number is changed. Automatically mute tracks when they are hidden. Transpose down one semitone Transpose up one semitone Transpose down one octave Transpose up one octave The change of emulator will be effective after restarting the program. Zmiana emulatora dokona się po restarcie programu. Warp the cursor around the edges of the pattern editor. Przemieszcza kursor wokół krańców edytora wzorców. Move to previous or next order when reaching top or bottom in the pattern editor. Przechodzi do poprzedniej lub następnej klatki po osiągnięciu początku lub końca wzorca. Display row numbers and the playback position on the status bar in hexadecimal. Wyświetla numery wierszów i pozycję odtwarzania na pasku stanu w systemie szesnastkowym. Preview previous and next orders in the pattern editor. Pokazuje fragmenty poprzednich i następnych klatek w edytorze wzorców. Create a backup copy of the existing file when saving a module. Tworzy kopię zapasową istniejącego pliku podczas zapisywania modułów. Don't select the whole track when double-clicking in the pattern editor. Nie zaznacza całego kanału poprzez podwójne kliknięcie w edytorze wzorców. Reverse the order of FM volume so that 00 is the quietest in the pattern editor. Reverse the order of FM volume so that 00 is the quietest in the pattern editor Odwraca kolejność poziomów głośności FM w edytorze wzorców w taki sposób, że 00 jest najcichszym ustawieniem. Move the cursor to right after entering effects in the pattern editor. Kursor zostanie przesunięty w prawo po wpisaniu efektu w edytorze wzorców. Reconstruct the current channel's state from previous orders upon playing. Rekonstruuje stan aktualnego kanału podczas odtwarzania przy pomocy danych z poprzednich klatek. Translate to your language from the next launch. See readme to check supported languages. Włącza tłumaczenie na twój język przy następnym uruchomieniu. Sprawdź plik readme by dowiedzieć się o obsługiwanych językach. Display FM detune values as signed numbers in the FM envelope editor. Pokazuje wartości rozstroju FM jako jako liczby ze znakiem w edytorze obwiedni FM. Fill 00 to effect value column upon entering effect id. Dodaje 00 do kolumny wartości efektów po wpisaniu ID efektu. Move the cursor position by cell with horizontal scroll bar in the order list and the pattern editor. Przesuwa pozycję kursora o komórkę przy pomocy pionowego paska przewijania w liscie klatek i w edytorze wzorców. Overwrite unused and unedited instrument properties on creating new properties. When disabled, override unused properties regardless of editing. Nadpisuje nieużywane i niezedytowane właściwości instrumentu przy tworzeniu nowych właściwości. Gdy wyłączone, nieużywane właściwości bedą zastępowane niezależnie od tego, czy były edytowane. Send only ADPCM samples used by instruments to the ADPCM memory. Recommend to turn off if you change ADPCM samples frequently due to take the high rewriting cost. Wysyła do pamięci ADPCM tylko próbki używane przez instrumenty. Zalecane jest wyłączenie tej opcji jeżeli często zmieniasz próbki ADPCM ze względu na długi czas nadpsywania. Correspond the instrument number in patterns when the instrument changes its number. Podmienia numer instrumentu we wzorcach gdy instrument zmieni swój numer. Set maximum volume during jam mode. When unchecked, the volume is changed by the volume spinbox. Ustawia maksymalną głośność poczas trybu improwizacji. Gdy odznaczone, głośność jest zmieniana przy pomocy selektora głośńości. Mute hidden tracks when visibility of tracks is changed. Wycisza ukryte kanały kiedy widoczność tych kanałów jest zmieniona. Restore the previous track visibility on startup. Przywraca poprzednią widoczność kanałów przy następnym uruchomieniu. Play and stop Odtwórz i zatrzymaj Play Odtwarzaj Play from start Odtwarzaj od początku Play pattern Odtwarzaj wzorzec Play from cursor Odtwarzaj od kursora Play from marker Odtwarzaj od znacznika Play step Odtwórz krok Stop Zatrzymaj Focus on pattern editor Skup widok na edytorze wzorców Focus on order list Skup widok na liście klatek Focus on instrument list Skup widok na liście instrumentów Toggle edit/jam mode Włącz tryby Edit/Jam Set marker Ustaw znacznik Paste and mix Wklej i miksuj Paste and overwrite Wklej i nadpisz Paste and insert Wklej i dodaj Select all Zaznacz wszystko Deselect Odznacz Select row Zaznacz wiersz Select column Zaznacz kolumnę Select pattern Wybierz wzorzec Select order Wybierz klatkę Go to step Idź do kroku Toggle track Włącz kanał Solo track Wyizoluj kanał Interpolate Dodaj Reverse Odwróć Go to previous order Przejdź do poprzedniego klatki Go to next order Przejdź do następnej klatki Toggle bookmark Włącz zakładkę Previous bookmark Poprzednia zakładka Next bookmark Następna zakładka Transpose, decrease note Transponuj, zwiększ nutę Transpose, increase note Transponuj, zwiększ nutę Transpose, decrease octave Transponuj, zmniejsz oktawę Transpose, increase octave Transponuj, zwiększ oktawę Previous instrument Poprzedni instrument Next instrument Następny instrument Mask instrument Maska instrumentu Mask volume Maska poziomu głośności Edit instrument Edytuj instrument Follow mode Tryb podążania Duplicate order Duplikuj wzorzec Clone patterns Klonuj wzorce Clone order Klonuj klatkę Replace instrument Zastąp instrument Expand pattern Rozszerz wzorzec Shrink pattern Zwiń wzorzec Fine decrease values Zmniejsz wartość o 1 Fine increase values Zwiększ wartość o 1 Coarse decrease values Zmniejsz wartość o 16 Coarse increase valuse Zmniejsz wartość o 16 Expand effect column Rozszerz kolumnę efektów Shrink effect column Zwiń kolumnę efektów Previous highlighted step Poprzedni podświetlny krok Next highlighted step Następny podświetlony krok Increase pattern size Zwiększ rozmiar wzorca Decrease pattern size Zmniejsz rozmiar wzorca Increase edit step Zwiększ krok podczas edycji Decrease edit step Zmniejsz krok podczas edycji Display effect list Wyświetl listę efektów Previous song Poprzednia piosenka Next song Następna piosenka Jam volume up Podnieś głośność przy improwizacji Jam volume down Obniż głośność przy improwizacji None Żaden Description: %1 Opis: %1 Set %1 Zestaw %1 Open color scheme Otwórz układ kolorów ini file (*.ini) All files (*) Wszystkie pliki (*) Error Błąd An unknown error occurred while loading the color scheme. Wystąpił nieoczekiwany błąd podczas ładowania układu kolorów. Save color scheme Zapisz układ kolorów Failed to save the color scheme. Nie udało się zapisać układu kolorów. EffectDescription Arpeggio, x: 2nd note (0-F), y: 3rd note (0-F) Arpeggio, x: druga nuta(0-F), y: trzecia nuta(0-F) Portamento up, xx: depth (00-FF) Portamento w górę, xx: głębokość(00-FF) Portamento down, xx: depth (00-FF) Portamento w dół, xx: głębokość(00-FF) Tone portamento, xx: depth (00-FF) Portamento tonu, xx: głębokość(00-FF) Vibrato, x: period (0-F), y: depth (0-F) Vibrato, x: okres(0-F), y: głębokość(0-F) Tremolo, x: period (0-F), y: depth (0-F) Tremolo, x: okres(0-F), y: głębokość(0-F) Pan, xx: 00 = no sound, 01 = right, 02 = left, 03 = center Ustawienie stereo, xx: 00 = cisza, 01 = prawo, 02 = lewo, 03 = środek Volume slide, x: up (0-F), y: down (0-F) Zjazd poziomu głośności, x: w górę(0-F), y: w dół(0-F) Jump to beginning of order xx Przeskocz do początku klatki xx End of song Koniec piosenki Jump to step xx of next order Przeskocz do kroku xx następnego klatki Change speed (xx: 00-1F), change tempo (xx: 20-FF) Zmień szybkość (xx: 00-1F), zmień tempo (xx: 20-FF) Note delay, xx: count (00-FF) Opóźnienie nuty, xx: ilość(00-FF) Auto envelope, x: shift amount (0-F), y: shape (0-F) Automatyczna obwiednia, x: przesunięcie(x-8), y: kształt(0-F) Hardware envelope period 1, xx: high byte (00-FF) Okres wykonania sprzętowej obwiedni 1, xx: wysoki bajt(00-FF) Hardware envelope period 2, xx: low byte (00-FF) Okres wykonania sprzętowej obiedni 2, xx: niski bajt(00-FF) Retrigger, x: volume slide (0-7: up, 8-F: down), y: tick (1-F) i don't have bloody idea how to translate that, I'm sorry Wyzwól ponownie, x: zjazd poziomu głośności (0-7: w górę, 8-F: w dół), y: tick (1-F) Set groove xx Ustaw Groove o wartości xx Detune, xx: pitch (00-FF) Rozstrój, xx: wysokość w centach(00-FF) Note slide up, x: count (0-F), y: seminote (0-F) Zjazd nuty w górę, x: ilość(0-F), y: półton(0-F) Note slide down, x: count (0-F), y: seminote (0-F) Zjazd nuty w dół, x: ilość(0-F), y: półton(0-F) Note cut, xx: count (00-FF) Odcięcie nuty, xx: ilość(00-FF) Transpose delay, x: count (0-7: up, 8-F: down), y: seminote (0-F) Opóźnienie transponowania, x: ilość(0-7: góra, 8-F: dół), y: pólton(0-F) Volume delay, x: count (0-F), yy: volume (00-FF) Opóżnienie głośności, x: ilość(0-F), yy: głośność (00-FF) Note cut, xx: count (01-FF) Odcięcie nuty, xx: ilość(01-FF) Transpose delay, x: count (1-7: up, 9-F: down), y: seminote (0-F) Opóźnienie transponowania, x: ilość(1-7: góra, 9-F: dół), y: pólton(0-F) Tone/Noise mix, xx: 00 = no sound, 01 = tone, 02 = noise, 03 = tone & noise Mikser tonu/szumu, xx: 00=cisza, 01=ton, 02=szum, 03=ton & szum Master volume, xx: volume (00-3F) Główny poziom głośności, xx: głośność (00-3F) Noise pitch, xx: pitch (00-1F) Częstotliwość szumu, xx: wysokość (00-1F) Register address bank 0, xx: address (00-6B) Bank adresów rejestru 0, xx: adres (00-B6) Register address bank 1, xx: address (00-6B) Bank adresów rejestru 1, xx: adres (00-B6) Register value set, xx: value (00-FF) Ustaw wartości rejestrów, xx: wartość rejestru (00-FF) AR control, x: operator (1-4), yy: attack rate (00-1F) Kotrola AR, x: operator(1-4), yy: wartość AR (00-1F) Brightness, xx: relative value (01-FF) Brightness, xx: wartość względna (01-FF) DR control, x: operator (1-4), yy: decay rate (00-1F) Kontrola DR, x: operator(1-4), yy: wartość DR (00-1F) Envelope reset, xx: count (00-FF) Resetuj obwiednię, xx: ilość (00-FF) FB control, xx: feedback value (00-07) Kontrola FB, xx: ilość sygnału zwrotnego (00-07) Fine detune, xx: pitch (00-FF) Rozstrojenie, xx: wysokość (00-FF) ML control, x: operator (1-4), y: multiple (0-F) Kontrola ML, x: operator(1-4), y: wartość ML (0-F) Volume delay, x: count (1-F), yy: volume (00-FF) Opóżnienie głośności, x: ilość(1-F), yy: głośność (00-FF) RR control, x: operator (1-4), y: release rate (0-F) Kontrola RR, x: operator(1-4), y: wartość RR (0-F) TL control, x: operator (1-4), yy: total level (00-7F) Kontrola TL, x: operator(1-4), yy: wartość TL (00-7F) Invalid effect Nieprawidłowy efekt EffectListDialog Effect list Lista efektów Effect Efekt Track type Typ utworu Description Opis Rhythm Rytm FMEnvelopeSetEditDialog Edit FM envelope set Edytuj zestaw obwiedni FM Add Dodaj Set digit types in the order of appearance. Ustaw typ cyfer wedle kolejności występowania. Remove Usuń Number Numer Type Typ Skip Pomiń FMOperatorTable SSGEG Type Typ Operator %1 Operator %1 Copy envelope Kopiuj obwiednię Paste envelope Wklej obwiednię Paste envelope From Wklej obwiednię z... Copy operator Kopiuj operator Paste operator Wklej operator FileIOErrorMessageBox module moduł s98 S98 vgm VGM wav WAV bank bank brzmień instrument instrument Path does not exist. Ścieżka do podanego pliku nie istnieje. Unsupported file format. Niewspierany format pliku. Could not load the %1 properly. Please make sure that you have the latest version of BambooTracker. Nie udało się wczytać %1. Upewnij się że masz najnowszą wersję Bamboo Trackera. Could not load the %1. It may be corrupted. Stopped at %2. Nie udało się wczytać %1. Najprawdopodobniej jest uszkodzony. Zatrzymano na: %2。 Failed to load %1. Nie udało sie wczytać %1. Failed to export to %1. Nie udało się wyeksportować %1. Failed to save the %1. Nie udało się zapisać %1. Error Błąd Could not open the file. Nie udało się otworzyć pliku. FileType module moduł s98 S98 vgm VGM wav WAV bank bank brzmień instrument instrument GoToDialog Go To Idź do Order Klatka Step Krok Track Kanał GridSettingsDialog Grid Settings Ustawienia siatki Interval Przerwa GrooveSettingsDialog Groove Settings Ustawienia Groove Remove Usuń Add Dodaj Sequence Sekwencja Copy Fxx Kopiuj Fxx Expand Rozszerz Generate Generuj Shrink Zwiń Pad Pad Speed: %1 Szybkość: %1 HideTracksDialog Hide Tracks Ukryj kanały Check All Zaznacz wszystko Reverse Odwróć Set the visibility of tracks: Ustaw widoczność kanałów: InstrumentEditorADPCMForm Users: Używane przez: Sample Próbka Envelope Obwiednia Arpeggio Arpeggio Type: Typ: Pitch Wysokość Panning idk Panning stereo Absolute Całkowity Fixed Stały Relative Względny InstrumentEditorDrumkitForm Sample assignment Przypisanie próbki Key Klawisz Pitch Wysokość Pan pan Panning Pannning stereo Sample Próbka InstrumentEditorFMForm Envelope Obwiednia Users: Używane przez: LFO/Operator sequence Sekwencja operatora LFO LFO Start count: Rozpocznij liczenie: AM operators Operatory AM Operator 1 Operator 1 Operator 2 Operator 2 Operator 3 Operator 3 Operator 4 Operator 4 Envelope reset Resetuj obwiednię Reset envelope before key on Resetuj obwiednię przed naciśnięciem klawisza Panning Panning stereo Operator sequence Sekwencja operatorów Operator: Operator: Sequence Sekwencja FM 3ch FM 3ch Arpeggio/Pitch Wysokość tonu / arpeggio Arpeggio Arpeggio Type: Typ: Pitch Wysokość Absolute Freq All Fixed Stały Relative Względny Error Błąd Did not match the clipboard text format with %1. Nie udało się dopasować zawartości schowka do %1. Copy envelope Kopiuj obwiednię Paste envelope Wklej obwiednię Paste envelope From Wklej obwiednię z... Copy LFO parameters Kopiuj parametry LFO Paste LFO parameters Wklej parametry LFO InstrumentEditorSSGForm Waveform Przebieg fali Users: Używane przez: Square mask Maska fali kwadratowej Raw Surowy Tone/Mask ratio Współczynik ton/maska Hardware envelope frequency Częstotliwość sprzętowej obwiedni Tone/Env ratio Wspołczynnik ton/obw. Tone/Noise Ton/Szum Envelope Obwiednia Arpeggio Arpeggio Type: Typ: Pitch Wysokość Sq Tri Saw InvSaw SMTri SMSaw SMInvSaw HEnv %1 Absolute Całkowity Fixed Stały Relative Względny InstrumentSelectionDialog Select instruments Wybierz instrumenty Search... Wyszukaj... KeySignatureManagerForm Key Signature Manager Menedżer znaków przykluczowych Key signature editor Edytor znaków przykluczowych Key Klawisz Order Klatka Step Krok Create New Utwórz nowy Update Odśwież Remove Usuń Clear All Wyczyść wszystko KeyboardShortcutListDialog Keyboard shortcuts Skróty klawiszowe qrc:/doc/shortcuts MainWindow BambooTracker Order List Lista klatek Module Settings Ustawienia modułu Copyright Prawa autorskie Author Autor Edit settings Edytuj ustawienia Song Settings Ustawienia piosenki Speed Szybkość Tempo Tempo Untitled Bez nazwy Groove Groove Instruments Instrumenty &File Plik(&F) &Export Eksportuj(&E) &Edit Edytuj(&E) &Select Wybierz(&S) Paste Specia&l Wklej specjalny(&I) &Pattern Wzorzec&P) &Transpose Transponuj(&T) &Bookmarks Zakładki(&B) &Change Values Zmień wartości(&C) S&ong Piosenka(&O) &Module Moduł(&M) Clean&up Czyszczenie utworu(&U) &Instrument Instrument(&I) &Help Pomoc(&H) &New... Nowy plik(&N)... Ctrl+N &Open... Otwórz(&O)... Ctrl+O &Save Zapisz(&S) Ctrl+S Save &As... Zapisz jako(&A)... E&xit Wyjdź(&X) &Undo Cofnij zmiany(&U) Ctrl+Z &Redo Przerób(&R) Ctrl+Y Cu&t Wytnij(&T) Ctrl+X &Copy Kopiuj(&C) Ctrl+C &Paste Wklej(&P) Ctrl+V Ctrl+M &Delete Usuń(&D) Pattern size Rozmiar wzorca Step Krok Key repetition Powtarzanie klawisza Title Tytuł Songs Piosenki &Recent Files Ostatnio otwarte pliki(&R) Patter&n Wzorzec(&N) &Tracker Tracker(&T) Vie&w Widok(&W) Main toolbar Główny pasek narzędzi Secondary toolbar Poboczny pasek narzędzi Del &All Wszystkie(&A) Ctrl+A &None Żaden(&N) Esc E&xpand Rozszerz(&X) S&hrink Zwiń(&H) &Decrease Note Zmniejsz nutę(&D) Ctrl+F1 &Increase Note Zwiększ nutę&I) Ctrl+F2 D&ecrease Octave Zmniejsz oktawę(&E) Ctrl+F3 I&ncrease Octave Zwiększ oktawę(&N) Ctrl+F4 &Insert Order Wstaw klatkę(&I) &Remove Order Usuń klatkę(&R) &Module Properties... Właściwości Modułu(&M)... Ctrl+P &New Instrument Nowy instrument(&N) &Remove Instrument Usuń instrument(&R) &Clone Instrument Klonuj Instrument(&C) &Deep Clone Instrument Głęboko sklonuj instrument(&D) &Load From File... Wczytaj z pliku(&L)... &Save To File... Zapisz do pliku(&S)... &Edit... Wyjdź(&E)... Ctrl+I &Play Odtwarzaj(&P) Play P&attern Odtwarzaj wzorzec(&A) F6 Play &From Start Odtwarzaj od początku(&F) F5 Play From C&ursor Odtwarzaj od pozycji kursora(&U) F7 &Stop Zatrzymaj(&S) F8 &Edit Mode Tryb Edycji(&E) Space To&ggle Track Włącz kanał(&G) Alt+F9 S&olo Track Wyizoluj kanał(&O) Alt+F10 &Kill Sound Wycisz dźwięk(&K) F12 &About... O programie(&A)... Fo&llow Mode Tryb podążania(&L) ScrollLock &Groove Settings... Ustawienia Groove(&G)... &Configuration... Konfiguracja(&C)... &Duplicate Order Duplikuj klatkę(&D) Ctrl+D Move Order &Up Przesuń klatkę do góry(&U) Move Order Do&wn Przesuń klatkę w dół(&W) &Clone Patterns Klonuj wzorce(&C) Alt+D Clone &Order Klonuj klatkę(&O) &Comments... Komentarze&C)... &Interpolate Wstaw(&P) Ctrl+G &Reverse Odwróć(&R) Ctrl+R R&eplace Instrument Zastąp instrument(&E) Alt+S &Row Wiersz(&R) &Column Kolumna(&C) &Order Klatka(&O) Remove Unused &Instruments Usuń nieużywane instrumenty(&I) Remove Unused &Patterns Usuń nieużywane wzorce(&P) &WAV... &VGM... &Overwrite Paste Overwrite Wklej i nadpisz(&O) &Clear Wyczyść(&C) &Effect List... Lista efektów(&E)... Effect List Lista efektów F1 &Shortcuts... Skróty klawiszowe(&S)... E&xport To Bank File... Eksportuj do banku brzmień(&X)... Remove &Duplicate Instruments Usuń zduplikowane instrumenty(&D) Re&name Instrument Zmień nazwę instumentu(&N) &Bookmark Manager... Menedżer Zakładek(&B)... Fine &Decrease Values Zmniejsz wartości o 1(&D) Shift+F1 Fine &Increase Values Zwiększ wartości o 1(&I) Shift+F2 Coarse D&ecrease Values Zmniejsz wartości o 16(&E) Shift+F3 Coarse I&ncrease Values Zwiększ wartości o 16(&N) Shift+F4 &Toggle Bookmark Włącz Zakładkę(&T) Ctrl+K &Next Bookmark Następna Zakładka(&N) Ctrl+PgDown &Previous Bookmark Poprzednia Zakładka(&P) Ctrl+PgUp &Instrument Mask Maska instrumentu(&I) &Volume Mask Maska poziomu głośności(&V) Set Ro&w Marker Ustaw znacznik wiersza(&W) Ctrl+B Play From &Marker Odtwarzaj od znacznika(&M) Ctrl+F7 &Go To... Idź do(&G)... Alt+G Remove Unused &ADPCM Samples Usuń nieużywane próbki ADPCM(&A) &Status Bar Pasek stanu&S) &Toolbar Pasek narzędzi(&T) New Drumki&t Nowy zestaw instrumentów perkusyjnych(&T) &Wave View Widok fali(&W) &Transpose Song... Transponuj piosenkę(&T)... &Swap Tracks... Zamień kanały(&S)... &Insert Wstaw(&I) &Cursor Kursor(&C) &Selection Wybór(&S) &Fill Wypełnij(&F) &Hide Tracks... Ukryj kanały(&H)... &Estimate Song Length... Oszacuj długość piosenki(&E)... &Key Signature Manager... &Menedżer znaków przykluczowych... &Welcome... &Witamy... &Mix Miksuj(&M) &Import From Bank File... Importuj z banku brzmień(&I)... &S98... Octave Oktawa Octave: %1 Octave: Oktawa: %1 Save changes to %1? Save changes to Zapisać zmiany do %1? Error Błąd Instrument %1 Instrument Instrument %1 Welcome to BambooTracker v%1! Witamy w programie BambooTracker v%1! Open instrument Otwórz instrument Save instrument Zapisz instrument Open bank Otwórz bank brzmień Select instruments to load: Wybierz instrument: No instrument Brak instrumentów Standard Standartowy English Default notation system Set the name of suitable notation system (English or German) Angielski Volume Poziom głośności Step highlight 1st Pierwsze podkreślenie kroku 2nd Drugie Welcome to BambooTracker! Witamy w programie BambooTracker! Don't know where to start? Nie wiesz gdzie zacząć? Check the demo modules and instruments included with your download of BambooTracker. Sprawdź piosenki demonstracyjne i instrumenty dołączone do twojej kopii BambooTrackera. Need a list of effects and shortcuts? Potrzebujesz listy efektów i skrótów klawiszowych? Check the Help menu at the top of the window. Sprawdź menu pomocy w górnej części okna. Still lost? Wciąż zagubiony? The README.md has a link to our Discord server. Plik README.md zawiera link do naszego serwera na Discordzie. Think you've found a bug? Missing a feature? Uważasz że znalazłeś błąd w programie? Brakującą funkcję? BambooTracker is still in development, bugs and missing features are to be expected. So we need your help! BambooTracker jest wciąż w fazie rozwoju, należy się spodziewać błedów i brakujących funkcji. Potrzebujemy więc Twojej pomocy! Please report any bugs you find and requests and features you'd like to see on our Discord server or our bug tracker (%1). Prosimy o zgłaszanie jakichkolwiek błedów i próśb o nowe funkcje na nasz serwer na Discordzie lub na naszego bugtrackera (%1). If you're a developer yourself or would like to start being one, consider contributing to the project yourself. Any help would be appreciated! Jeżeli jestes programistą, albo chcesz nim zostać, rozważ proszę wsparcie tego projektu. Każda pomoc jest doceniana! Welcome Witamy The number of instruments has reached the upper limit. Ilość instrumentów przekroczyła wartość maksymalną. Select instruments to save: Wybierz instrument do zapisu: Save bank Zapisz bank brzmień - Custom Niestandartowy PC-9821 with PC-9801-86 PC-9821 with Speak Board PC-88VA2 NEC PC-8801mkIISR FM3ch expanded Roszszerzenie FM3ch Insufficient memory size to load ADPCM samples. Please delete the unused samples. Niewystarczająca ilość pamięci, by wczytać próbki ADPCM. Proszę usunąć nieużywane próbki. The module has been changed. Do you want to save it? Moduł został zmieniony. Czy chcesz go zapisać? This software is licensed under the GNU General Public License v2.0 or later. To oprogramowanie jest objęte licencją GNU General Public Licence v2.0 lub późniejszą. Nuked OPN-MOD by (C) Alexey Khokholov (Nuke.YKT), Jean Pierre Cimalando (LGPL v2.1+) Nuked OPN-MOD autorstwa (C) Alexey Khokholov (Nuke.YKT) i Jean Pierre Cimalando (LGPL v2.1+) ymdeltat by (C) Tatsuyuki Satoh, Jarek Burczynski, ValleyBell (GPL v2+) ymdeltat autorstwa (C) Tatsuyuki Satoh, Jarek Burczynski, ValleyBell (GPL v2+) This software is licensed under the GNU General Public License v2.0. To oprogramowanie jest objete licencją GNU General Public License v2.0. <b>YM2608 Music Tracker<br>Copyright (C) 2018-2021 Rerrah</b><br><hr>Libraries:<br>- C86CTL by (C) honet (BSD 3-Clause)<br>- libOPNMIDI by (C) Vitaly Novichkov (MIT License part)<br>- MAME (MAME License)<br>- Nuked OPN-MOD by (C) Alexey Khokholov (Nuke.YKT)<br>and (C) Jean Pierre Cimalando (LGPL v2.1)<br>- RtAudio by (C) Gary P. Scavone (RtAudio License)<br>- RtMidi by (C) Gary P. Scavone (RtMidi License)<br>- SCCI by (C) gasshi (SCCI License)<br>- Silk icons by (C) Mark James (CC BY 2.5 or 3.0)<br>- Qt (GPL v2+ or LGPL v3)<br>- VGMPlay by (C) Valley Bell (GPL v2)<br><br>Also see changelog which lists contributors. <b>YM2608 Music Tracker<br>Copyright (C) 2018-2021 Rerrah</b><br><hr>Biblioteki:<br>- C86CTL by (C) honet (BSD 3-Clause)<br>- libOPNMIDI by (C) Vitaly Novichkov (część na licencji MIT)<br>- MAME (MAME License)<br>- Nuked OPN-MOD by (C) Alexey Khokholov (Nuke.YKT)<br>i (C) Jean Pierre Cimalando (LGPL v2.1)<br>- RtAudio by (C) Gary P. Scavone (RtAudio License)<br>- RtMidi by (C) Gary P. Scavone (RtMidi License)<br>- SCCI by (C) gasshi (SCCI License)<br>- Silk icons by (C) Mark James (CC BY 2.5 or 3.0)<br>- Qt (GPL v2+ lub LGPL v3)<br>- VGMPlay by (C) Valley Bell (GPL v2)<br><br>Zobacz również changelog, która wymienia współautorów. Could not initialize MIDI input. Nie udało się uruchomić wejścia MIDI. BambooTracker module (*.btm) All files (*) Wszystkie pliki (*) WAV signed 16-bit PCM (*.wav) Export %1 to WAV Eksportuj %1 do WAV VGM file (*.vgm) S98 file (*.s98) Do you want to remove all duplicate instruments? Czy chcesz usunąć wszystkie zduplikowane instrumenty? Do you want to remove all unused ADPCM samples? Czy chcesz usunąć wszystkie nieużywane próbki ADPCM? Do you want to transpose a song? Czy chcesz transponować piosenkę? Do you want to swap tracks? Czy chcesz zamienić kanały? Approximate song length: %1m%2s Przybliżona długość piosenki: %1m%2s Instrument: %1 Instrument: Instrument: %1 Do you want to change song properties? Czy chcesz zmienić właściwości piosenki? Change to jam mode Wejdź w tryb improwizacji Change to edit mode Wejdź w tryb edycji YM2608 Music Tracker Tracker muzyczny dla YM2608 Web: Strona: This software is licensed under the GNU General Public Lisence v2.0. To oprogramowanie jest objęte licencją GNU General Public Licence v2.0. Source is available at: Kod źródłowy jest dostępny na: Libraries: Biblioteki: Also see changelog which lists contributors. Zobacz także changelog wymieniający współautorów. Thank you to everyone who reports bugs, makes suggestions, and contributes to this project! Dziękujemy każdemu kto zgłasza błedy, sugeruje zmiany i wspomaga ten projekt! C86CTL by (C) honet (BSD 3-Clause) C86CTL autorstwa (C) honet (3-klauzulowa licencja BSD) emu2149 by (C) Mitsutaka Okazaki (MIT License) emu2149 autorstwa (C) Mitsutaka Okazaki (Licencja MIT) fmopn by (C) Tatsuyuki Satoh, Jarek Burczynski, ValleyBell (GPL v2+) fmopn autorstwa (C) Tatsuyuki Satoh, Jarek Burczynski, ValleyBell (GPL v2+) libOPNMIDI by (C) Vitaly Novichkov (MIT License part) libOPNMIDI autorstwa (C) Vitaly Novichkov (część objęta licencją MIT) MAME (MAME License) MAME (Licencja MAME) Nuked OPN-MOD by (C) Alexey Khokholov (Nuke.YKT), Jean Pierre Cimalando (LGPL v2.1) Nuked OPN-MOD autorstwa (C) Alexey Khokholov (Nuke.YKT), Jean Pierre Cimalando (LGPL v2.1) RtAudio by (C) Gary P. Scavone (RtAudio License) RtAudio autorstwa (C) Gary P. Scavone (Licencja RtAudio) RtMidi by (C) Gary P. Scavone (RtMidi License) RtMidi autorstwa (C) Gary P. Scavone (Licencja RtMidi) SCCI by (C) gasshi (SCCI License) SCCI autorstwa (C) gasshi (Licencja SCCI) Silk icons by (C) Mark James (CC BY 2.5 or 3.0) Ikony Silk autorstwa (C) Mark James (CC BY 2.5 lub 3.0) Qt (GPL v2+ or LGPL v3) Qt (GPL v2+ lub LGPL v3) VGMPlay by (C) Valley Bell (GPL v2) VGMPlay autorstwa (C) Valley Bell (GPL v2) About O programie Failed to backup module. Nie udało się zapisać kopii zapasowej modułu. <b>YM2608 (OPNA) Music Tracker<br>Copyright (C) 2018-2020 Rerrah</b><br><hr>Libraries:<br>- C86CTL by (C) honet (BSD 3-Clause)<br>- libOPNMIDI by (C) Vitaly Novichkov (MIT License part)<br>- MAME (MAME License)<br>- Nuked OPN-MOD by (C) Alexey Khokholov (Nuke.YKT)<br>and (C) Jean Pierre Cimalando (LGPL v2.1)<br>- RtAudio by (C) Gary P. Scavone (RtAudio License)<br>- RtMidi by (C) Gary P. Scavone (RtMidi License)<br>- SCCI by (C) gasshi (SCCI License)<br>- Silk icons by (C) Mark James (CC BY 2.5 or 3.0)<br>- Qt (GPL v2+ or LGPL v3)<br>- VGMPlay by (C) Valley Bell (GPL v2)<br><br>Also see changelog which lists contributors. <b>YM2608 (OPNA) Music Tracker<br>Copyright (C) 2018-2020 Rerrah</b><br><hr>Użyte biblioteki:<br>- C86CTL by (C) honet (BSD 3-Clause)<br>- libOPNMIDI by (C) Vitaly Novichkov (MIT License part)<br>- MAME (MAME License)<br>- Nuked OPN-MOD by (C) Alexey Khokholov (Nuke.YKT)<br>and (C) Jean Pierre Cimalando (LGPL v2.1)<br>- RtAudio by (C) Gary P. Scavone (RtAudio License)<br>- RtMidi by (C) Gary P. Scavone (RtMidi License)<br>- SCCI by (C) gasshi (SCCI License)<br>- Silk icons by (C) Mark James (CC BY 2.5 or 3.0)<br>- Qt (GPL v2+ or LGPL v3)<br>- VGMPlay by (C) Valley Bell (GPL v2)<br><br>Zobacz także Changelog który wymienia wszystkich współautorów programu. Save module Zapisz moduł Open module Otwórz moduł Do you want to remove all unused instruments? Czy chcesz usunąć wszystkie nieużywane instrumenty? Do you want to remove all unused patterns? Czy chcesz usunąć wszystkie nieużywane wzorce? Export to WAV Eksportuj do WAV Cancel Anuluj Export to VGM Eksportuj do VGM Export to S98 Eksportuj do S98 Warning Uwaga %1 If you execute this command, the command history is reset. %1 Jeżeli wykonasz to polecenie, historia poleceń się zresetuje. Could not open the audio stream. Please change the sound settings in Configuration. Nie udało się otworzyć strumienia audio. Proszę zmienić ustawienia dźwięku w sekcji Kofiguracja. Could not set the sample rate of the audio stream to %1Hz. Currently the stream runs on %2Hz instead. Nie udało się ustawić częstotliwośći próbkowania na %1Hz. Obecnie strumień działa na częstotliwości %2Hz. ModulePropertiesDialog Module properties Właściwości modułu Tick frequency Częstotliwość zegara 60Hz (NTSC) 50Hz (PAL) Custom Niestandardowy Mixer Mikser PC-9821 with PC-9801-86 PC-9821 with Speak Board PC-88VA2 PC-8801mkIISR Custom mixer Niestandardowy mikser FM SSG Set Ustaw Song control Ustawienia piosenki Song Piosenka Update Odśwież Remove Usuń Insert Wstaw Untitled Bez nazwy Number Numer Title Tytuł Song type Typ piosenki Standard Standardowy FM3ch expanded Z rozszerzeniem FM3ch ModuleSaveCheckDialog Save changes to %1? Zapisać zmiany do %1? Untitled Bez nazwy NotationSystem English Angielski German Niemiecki OrderListPanel &Insert Order Wstaw klatkę(&I) &Remove Order Usuń klatkę(&R) &Duplicate Order Duplikuj klatkę&D) &Clone Patterns Klonuj wzorce(&C) Clone &Order Klonuj klatkę(&O) Move Order &Up Przesuń klatkę do góry(&U) Move Order Do&wn Move Order Dow&n Przesuń klatkę w dół(&W) Cop&y Kopiuj(&Y) &Paste Wklej(&P) PanMacroEditor Right Prawo Left Lewo Panning Left Lewo Center Środek Right Prawo PatternEditorPanel &Undo Cofnij zmiany(&U) &Redo Przerób(&R) &Copy Kopiuj(&C) Cu&t Wytnij(&T) &Paste WKlej(&P) Paste Specia&l Wklej specjalny(&I) &Mix Miksuj(&M) &Overwrite Nadpisz(&O) &Insert Wstaw(&I) &Erase Wymaż(&E) Select &All Wybierz wszystko(&A) Patter&n Wzorzec(&N) &Interpolate Dodaj(&P) &Reverse Odwróć(&R) R&eplace Instrument Zamień instrument(&E) E&xpand Rozszerz(&X) S&hrink Zwiń(&H) &Transpose Transponuj(&T) &Decrease Note Zmniejsz nutę(&D) &Increase Note Zwiększ nutę(&I) D&ecrease Octave 1 Zmniejsz oktawę(&E) I&ncrease Octave 1 Zwiększ oktawę(&N) &Change Values Zmień wartości(&C) Fine &Decrease Values Zmniejsz wartośći o 1(&D) Fine &Increase Values Zwiększ wartości o 1(&I) Coarse D&ecrease Values Zmniejsz wartości o 16&E) Coarse I&ncrease Values Zwiększ wartości o 16(&N) To&ggle Track Włącz kanał(&G) &Solo Track Wyizoluj kanał(&S) Expand E&ffect Column Rozszerz kolumnę efektów(&F) Shrin&k Effect Column Zwiń kolumnę efektów(&K) &Unmute All Tracks Wyłącz wyciszenie dla wszystkich kanałów(&U) QObject Error Błąd An unknown error occurred. Wystąpił nieoczekiwany błąd. An unknown error occurred. %1 Wystąpił nieoczekiwany błąd. %1 S98ExportSettingsDialog S98 export settings Ustawienia eksportu do formatu S98 Tag Tag Title Title: Tytuł Artist Artist: Wykonawca Game Game: Gra Year Year: Rok Genre Genre: Gatunek Comment Komentarz Copyright Copyright: Copyright S98by S98by: S98przez System System: System NEC PC-9801 Target Cel FM YM2608 OPNA YM2612 OPN2 YM2203 OPN None Żaden SSG OPN internal Wbudowany w OPN AY-3-8910 PSG YM2149 PSG Support Wsparcie FM Channels Ilość kanałów FM Yes Tak Rhythm Rytm ADPCM Resolution Rozdzielczość No Nie SampleLengthDialog Resize Zmień rozmiar próbki ADPCM Length Długość SongType Standard Standardowy FM3ch expanded Z rozszerzeniem FM3 SwapTracksDialog Swap Tracks Zamień kanały Track 1 Kanał 1 Track 2 Kanał 2 ToneNoiseMacroEditor Tone Ton Noise Szum TransposeSongDialog Transpose Song Transponuj piosenkę Seminotes Półtony Exclude these instruments Wyklucz te instrumenty Reverse Odwróć Clear All Wyczyść wszysto VgmExportSettingsDialog VGM export settings Ustawienia eksportu do formatu VGM GD3 tag Tag GD3 Game Gra Name Name: Nazwa gry English Angielski Release date Release date: Data wydania Japanese Japoński System System: System NEC PC-9801 Track Utwór Title Title: Tytuł Author Author: Autor VGM file Plik VGM Creator Creator: Twórca Notes Notes: Uwagi Target Cel FM YM2608 OPNA YM2612 OPN2 YM2203 OPN YM2610B OPNB YM2610B OPNB None Żaden SSG OPN internal Wbudowany w OPN AY-3-8910 PSG YM2149 PSG Support Wsparcie FM Channels Ilość kanałów FM Yes Tak Rhythm Rytm ADPCM No Nie VisualizedInstrumentMacroEditor Size: 1 Rozmiar: 1 Size: %1 Rozmiar: %1 Loop Pętla Loop %1 Loop Pętla %1 Fixed Stały Release Opadanie Absolute Całkowity Relative Względny WaveExportSettingsDialog WAV export settings Ustawienia eksportu do formatu WAV Loop Ilość powtórzeń Sample rate Częstotliwość próbkowania Separate track export Eksportuj rozdzielone ścieżki Reverse Odwróć Check All Zaznacz wszystko effect_desc Arpeggio, x: 2nd note (0-F), y: 3rd note (0-F) Arpeggio, x: druga nuta(0-F), y: trzecia nuta(0-F) Portamento up, xx: depth (00-FF) Portamento w górę, xx: głębokość(00-FF) Portamento down, xx: depth (00-FF) Portamento w dół, xx: głębokość(00-FF) Tone portamento, xx: depth (00-FF) Portamento tonu, xx: głębokość(00-FF) Vibrato, x: period (0-F), y: depth (0-F) Vibrato, x: okres(0-F), y: głębokość(0-F) Tremolo, x: period (0-F), y: depth (0-F) Tremolo, x: okres(0-F), y: głębokość(0-F) Pan, xx: 00 = no sound, 01 = right, 02 = left, 03 = center Ustawienie stereo, xx: 00 = cisza, 01 = prawo, 02 = lewo, 03 = środek Volume slide, x: up (0-F), y: down (0-F) Zjazd poziomu głośności, x: w górę(0-F), y: w dół(0-F) Jump to beginning of order xx Przeskocz do początku klatki xx End of song Koniec piosenki Jump to step xx of next order Przeskocz do kroku xx następnego klatki Change speed (xx: 00-1F), change tempo (xx: 20-FF) Zmień szybkość (xx: 00-1F), zmień tempo (xx: 20-FF) Note delay, xx: count (00-FF) Opóźnienie nuty, xx: ilość(00-FF) Auto envelope, x: shift amount (0-F), y: shape (0-F) Automatyczna obwiednia, x: przesunięcie(x-8), y: kształt(0-F) Hardware envelope period 1, xx: high byte (00-FF) Okres wykonania sprzętowej obwiedni 1, xx: wysoki bajt(00-FF) Hardware envelope period 2, xx: low byte (00-FF) Okres wykonania sprzętowej obiedni 2, xx: niski bajt(00-FF) Retrigger, x: volume slide (0-7: up, 8-F: down), y: tick (1-F) Wyzwól ponownie, x: zjazd poziomu głośności (0-7: w górę, 8-F: w dół), y: tick (1-F) Set groove xx Ustaw Groove o wartości xx Detune, xx: pitch (00-FF) Rozstrój, xx: wysokość w centach(00-FF) Note slide up, x: count (0-F), y: seminote (0-F) Zjazd nuty w górę, x: ilość(0-F), y: półton(0-F) Note slide down, x: count (0-F), y: seminote (0-F) Zjazd nuty w dół, x: ilość(0-F), y: półton(0-F) Note cut, xx: count (00-FF) Odcięcie nuty, xx: ilość(00-FF) Transpose delay, x: count (0-7: up, 8-F: down), y: seminote (0-F) Opóźnienie transponowania, x: ilość(0-7: góra, 8-F: dół), y: pólton(0-F) Tone/Noise mix, xx: 00 = no sound, 01 = tone, 02 = noise, 03 = tone & noise Mikser tonu/szumu, xx: 00=cisza, 01=ton, 02=szum, 03=ton & szum Master volume, xx: volume (00-3F) Główny poziom głośności, xx: głośność (00-3F) Noise pitch, xx: pitch (00-1F) Częstotliwość szumu, xx: wysokość (00-1F) Register address bank 0, xx: address (00-6B) Bank adresów rejestru 0, xx: adres (00-B6) Register address bank 1, xx: address (00-6B) Bank adresów rejestru 1, xx: adres (00-B6) Register value set, xx: value (00-FF) Ustaw wartości rejestrów, xx: wartość rejestru (00-FF) AR control, x: operator (1-4), yy: attack rate (00-1F) Kotrola AR, x: operator(1-4), yy: wartość AR (00-1F) Brightness, xx: relative value (01-FF) Brightness, xx: wartość względna (01-FF) DR control, x: operator (1-4), yy: decay rate (00-1F) Kontrola DR, x: operator(1-4), yy: wartość DR (00-1F) Envelope reset, xx: count (00-FF) Resetuj obwiednię, xx: ilość (00-FF) FB control, xx: feedback value (00-07) Kontrola FB, xx: ilość sygnału zwrotnego (00-07) Fine detune, xx: pitch (00-FF) Rozstrojenie, xx: wysokość (00-FF) ML control, x: operator (1-4), y: multiple (0-F) Kontrola ML, x: operator(1-4), y: wartość ML (0-F) Volume delay, x: count (0-F), yy: volume (00-FF) Opóżnienie głośności, x: ilość(0-F), yy: głośność (00-FF) RR control, x: operator (1-4), y: release rate (0-F) Kontrola RR, x: operator(1-4), y: wartość RR (0-F) TL control, x: operator (1-4), yy: total level (00-7F) Kontrola TL, x: operator(1-4), yy: wartość TL (00-7F) Invalid effect Nieprawidłowy efekt BambooTracker-0.6.5/BambooTracker/lang/lang.pri000066400000000000000000000025521476276175200213530ustar00rootroot00000000000000# i18n generation & installation QM_FILES_INSTALL_PATH = $${DATA_INSTALL_PATH}/lang TRANSLATIONS += \ $$PWD/bamboo_tracker_fr.ts \ $$PWD/bamboo_tracker_ja.ts \ $$PWD/bamboo_tracker_pl.ts \ $$PWD/bamboo_tracker_es.ts \ equals(QT_MAJOR_VERSION, 5):lessThan(QT_MINOR_VERSION, 12) { message(Using a workaround for missing 'lrelease' option in Qt <5.12...) for(tsfile, TRANSLATIONS) { qmfile = $$tsfile qmfile ~= s/.ts$/.qm/ qmfile ~= s,/lang/,/.qm/, win32:$${qmfile}.commands = mkdir $$PWD/../.qm; else:$${qmfile}.commands = test -d $$PWD/../.qm/ || mkdir -p $$PWD/../.qm/; $${qmfile}.commands += lrelease -qm $$qmfile $$tsfile $${qmfile}.depends = $${tsfile} PRE_TARGETDEPS += $${qmfile} QMAKE_EXTRA_TARGETS += $${qmfile} translations_target = translations_$$basename(qmfile) $${translations_target}.depends = $$qmfile $${translations_target}.CONFIG = no_check_exist $${translations_target}.files = $$qmfile $${translations_target}.path = $$QM_FILES_INSTALL_PATH INSTALLS += $${translations_target} } } else { !versionAtLeast(QT_VERSION, 5.14.2) { message(Using a workaround for 'qm_files' target missing its install phase due to checking for the translations too early...) qm_files.CONFIG = no_check_exist } CONFIG += lrelease } BambooTracker-0.6.5/BambooTracker/main.cpp000066400000000000000000000120271476276175200204230ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "./gui/mainwindow.hpp" #include #include #include #include #include #include #include #include #include #include #include "configuration.hpp" #include "gui/q_application_wrapper.hpp" #include "gui/configuration_handler.hpp" namespace { // Localization static void setupTranslations(); static QString findQtTranslationsDir(); static QString findAppTranslationsDir(); } int main(int argc, char* argv[]) { try { std::shared_ptr config = std::make_shared(); bool hasSuccessed = io::loadConfiguration(config); #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor); #endif QApplicationWrapper a(argc, argv); if (config->getEnableTranslation()) setupTranslations(); a.setWindowIcon(QIcon(":/icon/app_icon")); QString filePath = (argc > 1) ? argv[argc - 1] : ""; // Last argument file MainWindow w(config, filePath, !hasSuccessed); QObject::connect(&a, &QApplication::applicationStateChanged, &w, &MainWindow::onApplicationStateChanged); w.show(); int ret = a.exec(); io::saveConfiguration(config); if (ret) QMessageBox::critical(nullptr, QObject::tr("Error"), QObject::tr("An unknown error occurred.")); return ret; } catch (std::exception& e) { QMessageBox::critical(nullptr, QObject::tr("Error"), QObject::tr("An unknown error occurred.\n%1").arg(e.what())); return 1; } } namespace { // Sets up the translation according to the current language void setupTranslations() { QApplication *a = qApp; const QString lang = QLocale::system().name(); QTranslator *qtTr = new QTranslator(a); QTranslator *appTr = new QTranslator(a); QString qtDir = findQtTranslationsDir(); QString appDir = findAppTranslationsDir(); if (!qtDir.isEmpty()) { QString baseName = "qt_" + lang; if (qtTr->load(baseName, qtDir)) { qDebug() << "Translation" << baseName << "from" << qtDir; } } if (!appDir.isEmpty()) { QString baseName = "bamboo_tracker_" + lang; if (appTr->load(baseName, appDir)) { qDebug() << "Translation" << baseName << "from" << appDir; } } a->installTranslator(qtTr); a->installTranslator(appTr); } // Finds the location of Qt translation catalogs QString findQtTranslationsDir() { #if defined(Q_OS_DARWIN) // if this is macOS, attempt to load from inside an app bundle QString pathInAppBundle = QApplication::applicationDirPath() + "/../Resources/lang"; if (QDir(pathInAppBundle).exists()) return pathInAppBundle; #endif #if defined(Q_OS_WIN) // if this is Windows, translations should be distributed with the program return QApplication::applicationDirPath() + "/lang"; #else // the files are located in the installation of Qt #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) return QLibraryInfo::path(QLibraryInfo::TranslationsPath); #else return QLibraryInfo::location(QLibraryInfo::TranslationsPath); #endif #endif } // Finds the location of our translation catalogs QString findAppTranslationsDir() { #ifndef QT_NO_DEBUG // if this is a debug build, attempt to load from the source directory QString pathInSources = QApplication::applicationDirPath() + "/.qm"; if (QDir(pathInSources).exists()) return pathInSources; #endif #if defined(Q_OS_DARWIN) // if this is macOS, attempt to load from inside an app bundle QString pathInAppBundle = QApplication::applicationDirPath() + "/../Resources/lang"; if (QDir(pathInAppBundle).exists()) return pathInAppBundle; #endif #if defined(Q_OS_WIN) // if this is Windows, translations should be distributed with the program return QApplication::applicationDirPath() + "/lang"; #else return QApplication::applicationDirPath() + "/../share/BambooTracker/lang"; #endif } } BambooTracker-0.6.5/BambooTracker/midi/000077500000000000000000000000001476276175200177135ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/midi/midi.cpp000066400000000000000000000167341476276175200213540ustar00rootroot00000000000000/* * Copyright (C) 2019-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "midi.hpp" #include #include "RtMidi.h" #include "utils.hpp" namespace { // Defines constexpr int MIDI_BUFFER_SIZE = 8192; const std::string MIDI_INP_CLIENT_NAME = "BambooTracker Rx"; const std::string MIDI_INP_PORT_NAME = "BambooTracker MIDI In"; constexpr bool MIDI_INP_IGNORE_SYSEX = false; constexpr bool MIDI_INP_IGNORE_TIME = false; constexpr bool MIDI_INP_IGNORE_SENSE = true; } std::unique_ptr MidiInterface::instance_; MidiInterface& MidiInterface::getInstance() { if (instance_) return *instance_; MidiInterface *out = new MidiInterface; instance_.reset(out); return *out; } MidiInterface::MidiInterface() : hasOpenInputPort_(false) {} MidiInterface::~MidiInterface() = default; std::string MidiInterface::currentApiName() const { RtMidi::Api api = inputClient_ ? inputClient_->getCurrentApi() : RtMidi::RTMIDI_DUMMY; return RtMidi::getApiDisplayName(api); } std::vector MidiInterface::getAvailableApis() const { std::vector apis; RtMidi::getCompiledApi(apis); std::vector list; for (const auto& apiAvailable : apis) list.push_back(RtMidi::getApiDisplayName(apiAvailable)); return (list.empty() ? std::vector({ "" }) : list); } bool MidiInterface::isAvailableApi(const std::string& api) const { const std::vector apis = getAvailableApis(); return (utils::find(apis, api) != apis.end()); } bool MidiInterface::switchApi(std::string api, std::string* errDetail) { std::vector apis; RtMidi::getCompiledApi(apis); for (const auto& apiAvailable : apis) { if (api == RtMidi::getApiDisplayName(apiAvailable)) { if (inputClient_ && apiAvailable == inputClient_->getCurrentApi()) return true; RtMidiIn *inputClient = nullptr; try { inputClient = new RtMidiIn(apiAvailable, MIDI_INP_CLIENT_NAME, MIDI_BUFFER_SIZE); if (errDetail) *errDetail = ""; } catch (RtMidiError &error) { error.printMessage(); if (errDetail) *errDetail = error.getMessage(); } if (inputClient) { inputClient->ignoreTypes(MIDI_INP_IGNORE_SYSEX, MIDI_INP_IGNORE_TIME, MIDI_INP_IGNORE_SENSE); inputClient->setCallback(&onMidiInput, this); } inputClient_.reset(inputClient); hasOpenInputPort_ = false; return (inputClient != nullptr); } } inputClient_ = nullptr; if (errDetail) *errDetail = "No available midi api."; return false; } bool MidiInterface::supportsVirtualPort() const { if (!inputClient_) return false; switch (inputClient_->getCurrentApi()) { case RtMidi::MACOSX_CORE: case RtMidi::LINUX_ALSA: case RtMidi::UNIX_JACK: return true; default: return false; } } bool MidiInterface::supportsVirtualPort(std::string api) const { std::vector apis; RtMidi::getCompiledApi(apis); RtMidi::Api apiType = RtMidi::RTMIDI_DUMMY; for (const auto& apiAvailable : apis) { if (api == RtMidi::getApiDisplayName(apiAvailable)) { apiType = apiAvailable; break; } } switch (apiType) { case RtMidi::MACOSX_CORE: case RtMidi::LINUX_ALSA: case RtMidi::UNIX_JACK: return true; default: return false; } } std::vector MidiInterface::getRealInputPorts() { if (!inputClient_) return { "" }; // Error RtMidiIn &client = *inputClient_; unsigned count = client.getPortCount(); std::vector ports; ports.reserve(count); for (unsigned i = 0; i < count; ++i) ports.push_back(client.getPortName(i)); return ports; } std::vector MidiInterface::getRealInputPorts(const std::string& api) { std::vector apis; RtMidi::getCompiledApi(apis); RtMidi::Api apiType = RtMidi::RTMIDI_DUMMY; for (const auto& apiAvailable : apis) { if (api == RtMidi::getApiDisplayName(apiAvailable)) { apiType = apiAvailable; break; } } if (apiType == RtMidi::RTMIDI_DUMMY) { return { "" }; // Error } std::vector ports; try { auto client = std::make_unique(apiType); unsigned count = client->getPortCount(); ports.reserve(count); for (unsigned i = 0; i < count; ++i) ports.push_back(client->getPortName(i)); } catch (RtMidiError& error) { error.printMessage(); } return ports; } void MidiInterface::closeInputPort() { if (!inputClient_) return; RtMidiIn &client = *inputClient_; if (hasOpenInputPort_) { client.closePort(); hasOpenInputPort_ = false; } } bool MidiInterface::openInputPort(unsigned port, std::string* errDetail) { if (!inputClient_) { if (errDetail) *errDetail = "Not opened input client."; hasOpenInputPort_ = false; return false; } try { RtMidiIn &client = *inputClient_; closeInputPort(); if (port == ~0u) { client.openVirtualPort(MIDI_INP_PORT_NAME); hasOpenInputPort_ = true; } else { client.openPort(port, MIDI_INP_PORT_NAME); hasOpenInputPort_ = client.isPortOpen(); } if (errDetail) *errDetail = ""; return true; } catch (RtMidiError& error) { if (errDetail) *errDetail = error.getMessage(); hasOpenInputPort_ = false; return false; } } bool MidiInterface::openInputPortByName(const std::string &portName, std::string* errDetail) { std::vector ports = getRealInputPorts(); for (unsigned i = 0, n = ports.size(); i < n; ++i) { if (ports[i] == portName) { return openInputPort(i, errDetail);; } } if (errDetail) *errDetail = "There is no port such the name."; return false; } void MidiInterface::installInputHandler(InputHandler* handler, void* user_data) { std::lock_guard lock(inputHandlersMutex_); inputHandlers_.push_back(std::make_pair(handler, user_data)); } void MidiInterface::uninstallInputHandler(InputHandler* handler, void* user_data) { std::lock_guard lock(inputHandlersMutex_); for (size_t i = 0, n = inputHandlers_.size(); i < n; ++i) { bool match = inputHandlers_[i].first == handler && inputHandlers_[i].second == user_data; if (match) { inputHandlers_.erase(inputHandlers_.begin() + i); return; } } } void MidiInterface::onMidiInput(double timestamp, std::vector *message, void *user_data) { MidiInterface *self = reinterpret_cast(user_data); std::unique_lock lock(self->inputHandlersMutex_, std::try_to_lock); if (!lock.owns_lock()) return; const uint8_t *msg = message->data(); size_t len = message->size(); for (size_t i = 0, n = self->inputHandlers_.size(); i < n; ++i) self->inputHandlers_[i].first(timestamp, msg, len, self->inputHandlers_[i].second); } BambooTracker-0.6.5/BambooTracker/midi/midi.hpp000066400000000000000000000046761476276175200213630ustar00rootroot00000000000000/* * Copyright (C) 2019-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include #include #include "RtMidi.h" class RtMidiIn; class MidiInterface { public: static MidiInterface& getInstance(); ~MidiInterface(); std::string currentApiName() const; std::vector getAvailableApis() const; bool isAvailableApi(const std::string& api) const; bool switchApi(std::string api, std::string* errDetail = nullptr); bool supportsVirtualPort() const; bool supportsVirtualPort(std::string api) const; std::vector getRealInputPorts(); std::vector getRealInputPorts(const std::string& api); void closeInputPort(); bool openInputPort(unsigned port, std::string* errDetail = nullptr); bool openInputPortByName(const std::string &portName, std::string* errDetail = nullptr); using InputHandler = void(double, const uint8_t*, size_t, void*); void installInputHandler(InputHandler *handler, void *userData); void uninstallInputHandler(InputHandler *handler, void *userData); private: static std::unique_ptr instance_; std::unique_ptr inputClient_; bool hasOpenInputPort_; std::mutex inputHandlersMutex_; std::vector> inputHandlers_; MidiInterface(); static void onMidiInput(double timestamp, std::vector *message, void *userData); }; BambooTracker-0.6.5/BambooTracker/module/000077500000000000000000000000001476276175200202565ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/module/effect.cpp000066400000000000000000000167021476276175200222240ustar00rootroot00000000000000/* * Copyright (C) 2019-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "effect.hpp" #include #include "step.hpp" namespace { const std::unordered_map CTOHEX = { { '0', 0 }, { '1', 1 }, { '2', 2 }, { '3', 3 }, { '4', 4 }, { '5', 5 }, { '6', 6 }, { '7', 7 }, { '8', 8 }, { '9', 9 }, { 'A', 10 }, { 'B', 11 }, { 'C', 12 }, { 'D', 13 }, { 'E', 14 }, { 'F', 15 } }; } namespace effect_utils { EffectType validateEffectId(SoundSource src, const std::string& id) { if (id == "00") { switch (src) { case SoundSource::FM: case SoundSource::SSG: case SoundSource::ADPCM: return EffectType::Arpeggio; default: return EffectType::NoEffect; } } else if (id == "01") { switch (src) { case SoundSource::FM: case SoundSource::SSG: case SoundSource::ADPCM: return EffectType::PortamentoUp; default: return EffectType::NoEffect; } } else if (id == "02") { switch (src) { case SoundSource::FM: case SoundSource::SSG: case SoundSource::ADPCM: return EffectType::PortamentoDown; default: return EffectType::NoEffect; } } else if (id == "03") { switch (src) { case SoundSource::FM: case SoundSource::SSG: case SoundSource::ADPCM: return EffectType::TonePortamento; default: return EffectType::NoEffect; } } else if (id == "04") { switch (src) { case SoundSource::FM: case SoundSource::SSG: case SoundSource::ADPCM: return EffectType::Vibrato; default: return EffectType::NoEffect; } } else if (id == "07") { switch (src) { case SoundSource::FM: case SoundSource::SSG: case SoundSource::ADPCM: return EffectType::Tremolo; default: return EffectType::NoEffect; } } else if (id == "08") { switch (src) { case SoundSource::FM: case SoundSource::RHYTHM: case SoundSource::ADPCM: return EffectType::Pan; default: return EffectType::NoEffect; } } else if (id == "0A") { switch (src) { case SoundSource::FM: case SoundSource::SSG: case SoundSource::ADPCM: return EffectType::VolumeSlide; default: return EffectType::NoEffect; } } else if (id == "0B") { return EffectType::PositionJump; } else if (id == "0C") { return EffectType::SongEnd; } else if (id == "0D") { return EffectType::PatternBreak; } else if (id == "0F") { return EffectType::SpeedTempoChange; } else if (id == "0G") { return EffectType::NoteDelay; } else if (id == "0H") { switch (src) { case SoundSource::SSG: return EffectType::AutoEnvelope; default: return EffectType::NoEffect; } } else if (id == "0I") { switch (src) { case SoundSource::SSG: return EffectType::HardEnvHighPeriod; default: return EffectType::NoEffect; } } else if (id == "0J") { switch (src) { case SoundSource::SSG: return EffectType::HardEnvLowPeriod; default: return EffectType::NoEffect; } } else if (id == "0K") { return EffectType::Retrigger; } else if (id == "0O") { return EffectType::Groove; } else if (id == "0P") { switch (src) { case SoundSource::FM: case SoundSource::SSG: case SoundSource::ADPCM: return EffectType::Detune; default: return EffectType::NoEffect; } } else if (id == "0Q") { switch (src) { case SoundSource::FM: case SoundSource::SSG: case SoundSource::ADPCM: return EffectType::NoteSlideUp; default: return EffectType::NoEffect; } } else if (id == "0R") { switch (src) { case SoundSource::FM: case SoundSource::SSG: case SoundSource::ADPCM: return EffectType::NoteSlideDown; default: return EffectType::NoEffect; } } else if (id == "0S") { return EffectType::NoteRelease; } else if (id == "0T") { switch (src) { case SoundSource::FM: case SoundSource::SSG: case SoundSource::ADPCM: return EffectType::TransposeDelay; default: return EffectType::NoEffect; } } else if (id == "0V") { switch (src) { case SoundSource::SSG: return EffectType::ToneNoiseMix; case SoundSource::RHYTHM: return EffectType::MasterVolume; default: return EffectType::NoEffect; } } else if (id == "0W") { switch (src) { case SoundSource::SSG: return EffectType::NoisePitch; default: return EffectType::NoEffect; } } else if (id == "0X") { return EffectType::RegisterAddress0; } else if (id == "0Y") { return EffectType::RegisterAddress1; } else if (id == "0Z") { return EffectType::RegisterValue; } else if (id == "B0") { switch (src) { case SoundSource::FM: return EffectType::Brightness; default: return EffectType::NoEffect; } } else if (id == "EA") { switch (src) { case SoundSource::FM: case SoundSource::SSG: case SoundSource::ADPCM: return EffectType::XVolumeSlide; default: return EffectType::NoEffect; } } else if (id == "ES") { return EffectType::NoteCut; } else if (id == "FB") { switch (src) { case SoundSource::FM: return EffectType::FBControl; default: return EffectType::NoEffect; } } else if (id == "FP") { switch (src) { case SoundSource::FM: case SoundSource::SSG: case SoundSource::ADPCM: return EffectType::FineDetune; default: return EffectType::NoEffect; } } else if (id == "ML") { switch (src) { case SoundSource::FM: return EffectType::MLControl; default: return EffectType::NoEffect; } } else if (id == "RR") { switch (src) { case SoundSource::FM: return EffectType::RRControl; default: return EffectType::NoEffect; } } else { switch (id.front()) { case 'A': switch (src) { case SoundSource::FM: return EffectType::ARControl; default: return EffectType::NoEffect; } case 'D': switch (src) { case SoundSource::FM: return EffectType::DRControl; default: return EffectType::NoEffect; } case 'M': return EffectType::VolumeDelay; case 'T': switch (src) { case SoundSource::FM: return EffectType::TLControl; default: return EffectType::NoEffect; } default: return EffectType::NoEffect; } } } Effect validateEffect(SoundSource src, const std::string& id, int value) { if (value == Step::EFF_VAL_NONE) return { EffectType::NoEffect, Step::EFF_VAL_NONE }; EffectType type = effect_utils::validateEffectId(src, id); int v; switch (type) { case EffectType::NoEffect: v = Step::EFF_VAL_NONE; break; case EffectType::VolumeDelay: case EffectType::TLControl: case EffectType::ARControl: case EffectType::DRControl: v = (CTOHEX.at(id[1]) << 8) | value; break; default: v = value; } return { type, v }; } } BambooTracker-0.6.5/BambooTracker/module/effect.hpp000066400000000000000000000045651476276175200222350ustar00rootroot00000000000000/* * Copyright (C) 2019-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include "step.hpp" #include "bamboo_tracker_defs.hpp" enum class EffectType { NoEffect, Arpeggio, PortamentoUp, PortamentoDown, TonePortamento, Vibrato, Tremolo, Pan, VolumeSlide, PositionJump, SongEnd, PatternBreak, SpeedTempoChange, NoteDelay, Groove, Detune, NoteSlideUp, NoteSlideDown, NoteRelease, TransposeDelay, MasterVolume, VolumeDelay, ToneNoiseMix, NoisePitch, HardEnvHighPeriod, HardEnvLowPeriod, AutoEnvelope, FBControl, TLControl, MLControl, ARControl, DRControl, RRControl, RegisterAddress0, RegisterAddress1, RegisterValue, Brightness, FineDetune, NoteCut, Retrigger, XVolumeSlide }; struct Effect { EffectType type; int value; }; namespace effect_utils { EffectType validateEffectId(SoundSource src, const std::string& id); Effect validateEffect(SoundSource src, const std::string& id, int value); inline Effect validateEffect(SoundSource src, const Step::PlainEffect& plain) { return validateEffect(src, plain.id, plain.value); } inline int reverseFmVolume(int volume, bool over0 = false) noexcept { return (volume < bt_defs::NSTEP_FM_VOLUME) ? (bt_defs::NSTEP_FM_VOLUME - 1 - volume) : over0 ? 0 : volume; } inline int reverseFmBrightness(int value) noexcept { return (value > 0) ? (0xff - value + 1) : value; } } BambooTracker-0.6.5/BambooTracker/module/module.cpp000066400000000000000000000074101476276175200222510ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "module.hpp" #include #include #include "utils.hpp" Module::Module(const std::string& filePath, const std::string& title, const std::string& author, const std::string& copyright, const std::string& comment, unsigned int tickFreq) : filePath_(filePath), title_(title), author_(author), copyright_(copyright), comment_(comment), tickFreq_(tickFreq), stepHl1Dist_(4), stepHl2Dist_(16), mixType_(MixerType::PC_9821_PC_9801_86), customLevelFM_(0), customLevelSSG_(0) { songs_.emplace_back(0); addGroove(); } size_t Module::getSongCount() const { return songs_.size(); } size_t Module::getGrooveCount() const { return grooves_.size(); } void Module::addSong(SongType songType, const std::string& title) { songs_.emplace_back(static_cast(songs_.size()), songType, title); } void Module::addSong(int n, SongType songType, const std::string& title, bool isUsedTempo, int tempo, int groove, int speed, size_t defaultPatternSize) { if (n < static_cast(songs_.size())) songs_.at(static_cast(n)) = Song(n, songType, title, isUsedTempo, tempo, groove, speed, defaultPatternSize); else songs_.emplace_back( n, songType, title, isUsedTempo, tempo, groove, speed, defaultPatternSize); } void Module::sortSongs(const std::vector& numbers) { std::vector newSongs; newSongs.reserve(songs_.size()); for (auto& n : numbers) { auto it = std::make_move_iterator(songs_.begin() + n); it->setNumber(static_cast(newSongs.size())); newSongs.push_back(*it); } songs_ = std::move(newSongs); } Song& Module::getSong(int num) { return *utils::findIf(songs_, [num](Song& s) { return s.getNumber() == num; });; } void Module::addGroove() { // Default groove is "6 6" grooves_.push_back({ 6, 6 }); } void Module::removeGroove(int num) { grooves_.erase(grooves_.begin() + num); } void Module::setGroove(int num, const std::vector& seq) { grooves_.at(static_cast(num)) = seq; } void Module::setGrooves(const std::vector>& seqs) { grooves_ = seqs; } Groove Module::getGroove(int num) const { return grooves_.at(static_cast(num)); } std::set Module::getRegisterdInstruments() const { std::set set; for (const Song& song : songs_) { auto&& subset = song.getRegisteredInstruments(); std::copy(subset.begin(), subset.end(), std::inserter(set, set.end())); } return set; } void Module::clearUnusedPatterns() { for (Song& song : songs_) song.clearUnusedPatterns(); } void Module::replaceDuplicateInstrumentsInPatterns(const std::unordered_map& map) { for (Song& song : songs_) song.replaceDuplicateInstrumentsInPatterns(map); } BambooTracker-0.6.5/BambooTracker/module/module.hpp000066400000000000000000000103421476276175200222540ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include #include "song.hpp" using Groove = std::vector; enum class MixerType : int { UNSPECIFIED = 0, CUSTOM = 1, PC_9821_PC_9801_86 = 2, PC_9821_SPEAK_BOARD = 3, PC_8801_VA2 = 4, PC_8801_MKII_SR = 5 }; class Module { public: Module(const std::string& filePath = "", const std::string& title = u8"", const std::string& author = u8"", const std::string& copyright = u8"", const std::string& comment = u8"", unsigned int tickFreq = 60); inline void setFilePath(const std::string& path) { filePath_ = path; } std::string getFilePath() const noexcept { return filePath_; }; inline void setTitle(const std::string& title) { title_ = title; } inline std::string getTitle() const noexcept { return title_; } inline void setAuthor(const std::string& author) { author_ = author; } inline std::string getAuthor() const noexcept { return author_; } inline void setCopyright(const std::string& copyright) { copyright_ = copyright; } inline std::string getCopyright() const noexcept { return copyright_; } inline void setComment(const std::string& comment) { comment_ = comment; } inline std::string getComment() const noexcept { return comment_; } inline void setTickFrequency(unsigned int freq) { tickFreq_ = freq; } inline unsigned int getTickFrequency() const noexcept { return tickFreq_; } inline void setStepHighlight1Distance(size_t dist) { stepHl1Dist_ = dist; } inline size_t getStepHighlight1Distance() const noexcept { return stepHl1Dist_; } inline void setStepHighlight2Distance(size_t dist) { stepHl2Dist_ = dist; } inline size_t getStepHighlight2Distance() const noexcept { return stepHl2Dist_; } size_t getSongCount() const; size_t getGrooveCount() const; inline void setMixerType(MixerType type) { mixType_ = type; } inline MixerType getMixerType() const noexcept { return mixType_; } inline void setCustomMixerFMLevel(double level) { customLevelFM_ = level; } inline double getCustomMixerFMLevel() const noexcept { return customLevelFM_; } inline void setCustomMixerSSGLevel(double level) { customLevelSSG_ = level; } inline double getCustomMixerSSGLevel() const noexcept { return customLevelSSG_; } void addSong(SongType songType, const std::string& title); void addSong(int n, SongType songType, const std::string& title, bool isUsedTempo, int tempo, int groove, int speed, size_t defaultPatternSize); void sortSongs(const std::vector& numbers); Song& getSong(int num); void addGroove(); void removeGroove(int num); void setGroove(int num, const std::vector& seq); void setGrooves(const std::vector>& seqs); Groove getGroove(int num) const; std::set getRegisterdInstruments() const; void clearUnusedPatterns(); void replaceDuplicateInstrumentsInPatterns(const std::unordered_map& map); private: std::string filePath_; std::string title_; std::string author_; std::string copyright_; std::string comment_; unsigned int tickFreq_; size_t stepHl1Dist_, stepHl2Dist_; std::vector songs_; std::vector grooves_; MixerType mixType_; double customLevelFM_, customLevelSSG_; }; BambooTracker-0.6.5/BambooTracker/module/pattern.cpp000066400000000000000000000072521476276175200224450ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "pattern.hpp" #include #include "effect.hpp" #include "note.hpp" #include "utils.hpp" namespace { constexpr size_t MAX_STEP_SIZE = 256; } Pattern::Pattern(int n, size_t defSize) : num_(n), size_(defSize), steps_(defSize), usedCnt_(0) { } Pattern::Pattern(int n, size_t size, const std::vector& steps) : num_(n), size_(size), steps_(steps), usedCnt_(0) { } Step& Pattern::getStep(int n) { return steps_.at(static_cast(n)); } size_t Pattern::getSize() const { for (size_t i = 0; i < size_; ++i) { for (int j = 0; j < Step::N_EFFECT; ++j) { if (!steps_[i].hasEffectValue(j)) continue; // "SoundSource::FM" is dummy, these effects are not related with sound source switch (effect_utils::validateEffectId(SoundSource::FM, steps_[i].getEffectId(j))) { case EffectType::PositionJump: case EffectType::SongEnd: case EffectType::PatternBreak: return i + 1; default: break; } } } return size_; } void Pattern::changeSize(size_t size) { if (size && size <= MAX_STEP_SIZE) { size_ = size; if (steps_.size() < size) steps_.resize(size); } } void Pattern::insertStep(int n) { if (n < static_cast(size_)) steps_.emplace(steps_.begin() + n); } void Pattern::deletePreviousStep(int n) { if (!n) return; steps_.erase(steps_.begin() + n - 1); if (steps_.size() < size_) steps_.resize(size_); } bool Pattern::hasEvent() const { auto endIt = steps_.cbegin() + static_cast(size_); return std::any_of(steps_.cbegin(), endIt, [](const Step& step) { return step.hasEvent(); }); } std::vector Pattern::getEditedStepIndices() const { auto endIt = steps_.cbegin() + static_cast(size_); return utils::findIndicesIf(steps_.cbegin(), endIt, [](const Step& step) { return step.hasEvent(); }); } std::set Pattern::getRegisteredInstruments() const { std::set set; for (size_t i = 0; i < size_; ++i) { const Step& step = steps_.at(i); if (step.hasInstrument()) set.insert(step.getInstrumentNumber()); } return set; } Pattern Pattern::clone(int asNumber) { return Pattern(asNumber, size_, steps_); } void Pattern::transpose(int semitones, const std::vector& excludeInsts) { for (size_t i = 0; i < size_; ++i) { Step& step = steps_.at(i); int note = step.getNoteNumber(); if (step.hasGeneralNote() && std::none_of(excludeInsts.begin(), excludeInsts.end(), [a = step.getInstrumentNumber()](int b) { return a == b; })) { step.setNoteNumber(utils::clamp(note + semitones, 0, Note::NOTE_NUMBER_RANGE - 1)); } } } void Pattern::clear() { steps_ = std::vector(size_); } BambooTracker-0.6.5/BambooTracker/module/pattern.hpp000066400000000000000000000040601476276175200224440ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include "step.hpp" class Pattern { public: Pattern(int n, size_t defSize); inline void setNumber(int n) noexcept { num_ = n; } inline int getNumber() const noexcept { return num_; } inline int increaseUsedCount() noexcept { return ++usedCnt_; } inline int decreaseUsedCount() noexcept { return --usedCnt_; } inline int getUsedCount() const noexcept { return usedCnt_; } Step& getStep(int n); size_t getSize() const; void changeSize(size_t size); void insertStep(int n); void deletePreviousStep(int n); bool hasEvent() const; std::vector getEditedStepIndices() const; std::set getRegisteredInstruments() const; Pattern clone(int asNumber); void transpose(int semitones, const std::vector& excludeInsts); void clear(); private: int num_; size_t size_; std::vector steps_; int usedCnt_; Pattern(int n, size_t size, const std::vector& steps); }; BambooTracker-0.6.5/BambooTracker/module/song.cpp000066400000000000000000000246301476276175200217350ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "song.hpp" #include #include #include #include "bamboo_tracker_defs.hpp" #include "utils.hpp" Bookmark::Bookmark(const std::string& argname, int argorder, int argstep) : name(argname), order(argorder), step(argstep) { } KeySignature::KeySignature(Type type, int scorder, int scstep) : type(type), order(scorder), step(scstep) { } Song::Song(int number, SongType songType, const std::string& title, bool isUsedTempo, int tempo, int groove, int speed, size_t defaultPatternSize) : num_(number), type_(songType), title_(title), isUsedTempo_(isUsedTempo), tempo_(tempo), groove_(groove), speed_(speed), defPtnSize_(defaultPatternSize), sigs_{ KeySignature(KeySignature::E, 0, 0) } { switch (songType) { case SongType::Standard: tracks_.reserve(15); for (int i = 0; i < 6; ++i) { tracks_.emplace_back(i, SoundSource::FM, i, defaultPatternSize); } for (int i = 0; i < 3; ++i) { tracks_.emplace_back(i + 6, SoundSource::SSG, i, defaultPatternSize); } for (int i = 0; i < 6; ++i) { tracks_.emplace_back(i + 9, SoundSource::RHYTHM, i, defaultPatternSize); } tracks_.emplace_back(15, SoundSource::ADPCM, 0, defaultPatternSize); break; case SongType::FM3chExpanded: tracks_.reserve(18); for (int i = 0; i < 9; ++i) { int ch = (i < 3) ? i : (i < 6) ? (i + 3) : (i - 3); tracks_.emplace_back(i, SoundSource::FM, ch, defaultPatternSize); } for (int i = 0; i < 3; ++i) { tracks_.emplace_back(i + 9, SoundSource::SSG, i, defaultPatternSize); } for (int i = 0; i < 6; ++i) { tracks_.emplace_back(i + 12, SoundSource::RHYTHM, i, defaultPatternSize); } tracks_.emplace_back(18, SoundSource::ADPCM, 0, defaultPatternSize); break; } } void Song::setDefaultPatternSize(size_t size) { defPtnSize_ = size; for (auto& t : tracks_) { t.changeDefaultPatternSize(size); } } size_t Song::getPatternSizeFromOrderNumber(int order) { if (static_cast(getOrderSize()) <= order) return 0; // Ilegal value size_t size = 0; for (auto& t : tracks_) { size_t ptnSize = t.getPatternFromOrderNumber(order).getSize(); size = !size ? ptnSize : std::min(size, ptnSize); } return size; } SongStyle Song::getStyle() const { SongStyle style; style.type = type_; style.trackAttribs = getTrackAttributes(); return style; } std::vector Song::getTrackAttributes() const { std::vector ret; ret.reserve(tracks_.size()); std::transform(tracks_.begin(), tracks_.end(), std::back_inserter(ret), [](const Track& track) { return track.getAttribute(); }); return ret; } Track& Song::getTrack(int num) { return tracks_.at(static_cast(num)); } void Song::changeType(SongType type) { if (std::exchange(type_, type) == type_) return; switch (type_) { case SongType::Standard: // Previous type: FM3chExpanded { const bool vis3ch = tracks_[2].isVisible() || tracks_[3].isVisible() || tracks_[4].isVisible() || tracks_[5].isVisible(); // Remove FM3-OP2,3,4 (track 3,4,5) tracks_.erase(tracks_.begin() + 3, tracks_.begin() + 6); for (size_t i = 3; i < tracks_.size(); ++i) { const auto attrib = tracks_[i].getAttribute(); tracks_[i].setAttribute(static_cast(i), attrib.source, attrib.channelInSource); } tracks_[2].setVisibility(vis3ch); break; } case SongType::FM3chExpanded: // Previous type: Standard const bool vis3ch = tracks_[2].isVisible(); // Expand FM3 track for (int i = 3; i < 6; ++i) { Track src = tracks_[2]; src.setAttribute(i, SoundSource::FM, i + 3); tracks_.insert(tracks_.begin() + i, std::move(src)); tracks_[static_cast(i)].setVisibility(vis3ch); } for (size_t i = 6; i < tracks_.size(); ++i) { const auto attrib = tracks_[i].getAttribute(); tracks_[i].setAttribute(static_cast(i), attrib.source, attrib.channelInSource); } break; } } std::vector Song::getOrderData(int order) const { std::vector ret; for (const Track& track : tracks_) { ret.push_back(track.getOrderInfo(order)); } return ret; } size_t Song::getOrderSize() const { return tracks_[0].getOrderSize(); } bool Song::canAddNewOrder() const { return tracks_[0].canAddNewOrder(); } void Song::insertOrderBelow(int order) { if (!canAddNewOrder()) return; for (Track& track : tracks_) { track.insertOrderBelow(order); } } void Song::deleteOrder(int order) { for (Track& track : tracks_) { track.deleteOrder(order); } } void Song::swapOrder(int a, int b) { for (Track& track : tracks_) { track.swapOrder(a, b); } } std::set Song::getRegisteredInstruments() const { std::set set; for (const Track& track : tracks_) { auto&& subset = track.getRegisteredInstruments(); std::copy(subset.begin(), subset.end(), std::inserter(set, set.end())); } return set; } void Song::clearUnusedPatterns() { for (Track& track : tracks_) track.clearUnusedPatterns(); } void Song::replaceDuplicateInstrumentsInPatterns(const std::unordered_map& map) { for (Track& track : tracks_) track.replaceDuplicateInstrumentsInPatterns(map); } void Song::transpose(int semitones, const std::vector& excludeInsts) { for (Track& track : tracks_) track.transpose(semitones, excludeInsts); } void Song::swapTracks(int track1, int track2) { auto it1 = utils::findIf(tracks_, [&](const Track& t) { return t.getAttribute().number == track1; }); if (it1 == tracks_.end()) throw std::invalid_argument("Invalid track number"); auto it2 = utils::findIf(tracks_, [&](const Track& t) { return t.getAttribute().number == track2; }); if (it2 == tracks_.end()) throw std::invalid_argument("Invalid track number"); TrackAttribute attrib1 = it1->getAttribute(); TrackAttribute attrib2 = it2->getAttribute(); it1->setAttribute(attrib2.number, attrib2.source, attrib2.channelInSource); it2->setAttribute(attrib1.number, attrib1.source, attrib1.channelInSource); std::iter_swap(it1, it2); } int Song::addBookmark(const std::string& name, int order, int step) { bms_.push_back(Bookmark(name, order, step)); return static_cast(bms_.size() - 1); } void Song::changeBookmark(int i, const std::string& name, int order, int step) { Bookmark& bm = bms_.at(static_cast(i)); bm.name = name; bm.order = order; bm.step = step; } void Song::removeBookmark(int i) { bms_.erase(bms_.begin() + i); } void Song::clearBookmark() { bms_.clear(); } void Song::swapBookmarks(int a, int b) { std::swap(bms_.at(static_cast(a)), bms_.at((static_cast(b)))); } void Song::sortBookmarkByPosition() { std::stable_sort(bms_.begin(), bms_.end(), [](Bookmark a, Bookmark b) { return ((a.order == b.order) ? (a.step < b.step) : (a.order < b.order)); }); } void Song::sortBookmarkByName() { std::stable_sort(bms_.begin(), bms_.end(), [](Bookmark a, Bookmark b) { return (a.name < b.name); }); } Bookmark Song::getBookmark(int i) const { return bms_.at(static_cast(i)); } std::vector Song::findBookmarks(int order, int step) const { std::vector idcs; for (size_t i = 0; i < bms_.size(); ++i) { const Bookmark& bm = bms_[i]; if (bm.order == order && bm.step == step) idcs.push_back(static_cast(i)); } return idcs; } std::vector Song::getSortedBookmarkList() const { std::vector tmp(bms_); std::stable_sort(tmp.begin(), tmp.end(), [](Bookmark a, Bookmark b) { return ((a.order == b.order) ? (a.step < b.step) : (a.order < b.order)); }); return tmp; } Bookmark Song::getPreviousBookmark(int order, int step) const { std::vector list = getSortedBookmarkList(); size_t i = 0; for (; i < list.size(); ++i) { Bookmark& bm = list.at(i); if (order < bm.order || (order == bm.order && step <= bm.step)) { break; } } return list.at((list.size() + i - 1) % list.size()); } Bookmark Song::getNextBookmark(int order, int step) const { std::vector list = getSortedBookmarkList(); size_t i = 0; for (; i < list.size(); ++i) { Bookmark& bm = list.at(i); if (order < bm.order || (order == bm.order && step < bm.step)) { break; } } return list.at(i % list.size()); } size_t Song::getBookmarkSize() const { return bms_.size(); } void Song::addKeySignature(KeySignature::Type key, int order, int step) { size_t i = 0; for (; i < sigs_.size(); ++i) { auto& ref = sigs_[i]; if (ref.order < order) continue; else if (order < ref.order) break; else { if (ref.step < step) continue; else if (step < ref.step) break; else { // Replace key ref.type = key; return; } } } sigs_.insert(sigs_.begin() + static_cast(i), KeySignature(key, order, step)); } void Song::changeKeySignature(int i, KeySignature::Type key, int order, int step) { sigs_.erase(sigs_.begin() + i); addKeySignature(key, order, step); } void Song::removeKeySignature(int i) { sigs_.erase(sigs_.begin() + i); } void Song::clearKeySignature() { sigs_ = { KeySignature(KeySignature::E, 0, 0) }; } KeySignature Song::getKeySignature(int i) const { return sigs_.at(static_cast(i)); } size_t Song::getKeySignatureSize() const { return sigs_.size(); } KeySignature::Type Song::searchKeySignatureAt(int order, int step) const { return std::lower_bound(sigs_.crbegin(), sigs_.crend(), KeySignature(KeySignature::E, order, step), [](const KeySignature& i, const KeySignature& v) { if (i.order == v.order) return (i.step > v.step); else return (i.order > v.order); })->type; } BambooTracker-0.6.5/BambooTracker/module/song.hpp000066400000000000000000000116431476276175200217420ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include #include "track.hpp" enum class SongType { Standard, FM3chExpanded }; struct SongStyle { SongType type; std::vector trackAttribs; // Always sorted by number }; struct Bookmark { std::string name = u8""; int order, step; Bookmark(const std::string& argname, int argorder, int argstep); }; struct KeySignature { enum Type : int { C = 0, CS = 1, DF = 2, D = 3, DS = 4, EF = 5, E = 6, FF = 7, ES = 8, F = 9, FS = 10, GF = 11, G = 12, GS = 13, AF = 14, A = 15, AS = 16, BF = 17, B = 18, CF = 19, BS = 20, FISRT = C, LAST = BS } type; int order, step; KeySignature(Type type, int scorder, int scstep); }; class Song { public: Song(int number, SongType songType = SongType::Standard, const std::string& title = u8"", bool isUsedTempo = true, int tempo = 150, int groove = 0, int speed = 6, size_t defaultPatternSize = 64); void setNumber(int n) noexcept { num_ = n; } int getNumber() const noexcept { return num_; } void setTitle(const std::string& title) { title_ = title; } std::string getTitle() const noexcept { return title_; } void setTempo(int tempo) noexcept { tempo_ = tempo; } int getTempo() const noexcept { return tempo_; } void setGroove(int groove) noexcept { groove_ = groove; } int getGroove() const noexcept { return groove_; } void toggleTempoOrGroove(bool isUsedTempo) noexcept { isUsedTempo_ = isUsedTempo; } bool isUsedTempo() const noexcept { return isUsedTempo_; } void setSpeed(int speed) noexcept { speed_ = speed; } int getSpeed() const noexcept { return speed_; } void setDefaultPatternSize(size_t size); size_t getDefaultPatternSize() const noexcept { return defPtnSize_; } size_t getPatternSizeFromOrderNumber(int order); SongStyle getStyle() const; std::vector getTrackAttributes() const; Track& getTrack(int num); void changeType(SongType type); std::vector getOrderData(int order) const; size_t getOrderSize() const; bool canAddNewOrder() const; void insertOrderBelow(int order); void deleteOrder(int order); void swapOrder(int a, int b); std::set getRegisteredInstruments() const; void clearUnusedPatterns(); void replaceDuplicateInstrumentsInPatterns(const std::unordered_map& map); void transpose(int semitones, const std::vector& excludeInsts); void swapTracks(int track1, int track2); // Bookmark int addBookmark(const std::string& name, int order, int step); void changeBookmark(int i, const std::string& name, int order, int step); void removeBookmark(int i); void clearBookmark(); void swapBookmarks(int a, int b); void sortBookmarkByPosition(); void sortBookmarkByName(); Bookmark getBookmark(int i) const; std::vector findBookmarks(int order, int step) const; Bookmark getPreviousBookmark(int order, int step) const; Bookmark getNextBookmark(int order, int step) const; size_t getBookmarkSize() const; // Key signature void addKeySignature(KeySignature::Type key, int order, int step); void changeKeySignature(int i, KeySignature::Type key, int order, int step); void removeKeySignature(int i); void clearKeySignature(); KeySignature getKeySignature(int i) const; size_t getKeySignatureSize() const; KeySignature::Type searchKeySignatureAt(int order, int step) const; static size_t getFMChannelCount(SongType type) { switch (type) { case SongType::Standard: return 6; case SongType::FM3chExpanded: return 9; default: throw std::invalid_argument("Invalid SongType."); } } private: int num_; SongType type_; std::string title_; bool isUsedTempo_; int tempo_; int groove_; int speed_; size_t defPtnSize_; std::vector tracks_; std::vector bms_; std::vector sigs_; std::vector getSortedBookmarkList() const; }; BambooTracker-0.6.5/BambooTracker/module/step.cpp000066400000000000000000000033721476276175200217420ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "step.hpp" const std::string Step::EFF_ID_NONE = "--"; Step::Step() : note_(NOTE_NONE), inst_(INST_NONE), vol_(VOLUME_NONE) { for (size_t i = 0; i < N_EFFECT; ++i) { eff_[i].id = EFF_ID_NONE; eff_[i].value = EFF_VAL_NONE; } } void Step::clear() { clearNoteNumber(); clearInstrumentNumber(); clearVolume(); for (size_t i = 0; i < N_EFFECT; ++i) { clearEffect(i); } } bool Step::hasEvent() const { if (!isEmptyNote()) return true; if (hasInstrument()) return true; if (hasVolume()) return true; for (int i = 0; i < N_EFFECT; ++i) { if (hasEffectId(i)) return true; if (hasEffectValue(i)) return true; } return false; } BambooTracker-0.6.5/BambooTracker/module/step.hpp000066400000000000000000000107441476276175200217500ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include class Step { public: Step(); enum NoteValue : int { NOTE_NONE = -1, // Special notes NOTE_KEY_OFF = -2, NOTE_ECHO0 = -3, NOTE_ECHO1 = -4, NOTE_ECHO2 = -5, NOTE_ECHO3 = -6, NOTE_KEY_CUT = -7, }; int getNoteNumber() const noexcept { return note_; } void setNoteNumber(int num) { note_ = num; } void setKeyOff() { note_ = NOTE_KEY_OFF; } void setKeyCut() { note_ = NOTE_KEY_CUT; } void setEchoBuffer(int n) { note_ = NOTE_ECHO0 - n; } void clearNoteNumber() noexcept { note_ = NOTE_NONE; } bool hasGeneralNote() const noexcept { return note_ > NOTE_NONE; } bool hasKeyOff() const noexcept { return note_ == NOTE_KEY_OFF; } bool hasKeyCut() const noexcept { return note_ == NOTE_KEY_CUT; } bool hasNoteEchoBuffer(int n) const { return note_ == (NOTE_ECHO0 - n); } bool isEmptyNote() const noexcept { return note_ == NOTE_NONE; } static bool testEmptyNote(int note) { return note == NOTE_NONE; } static constexpr int INST_NONE = -1; int getInstrumentNumber() const noexcept { return inst_; } void setInstrumentNumber(int num) { inst_ = num; } void clearInstrumentNumber() noexcept { inst_ = INST_NONE; } bool hasInstrument() const noexcept { return inst_ != INST_NONE; } static bool testEmptyInstrument(int inst) { return inst == INST_NONE; } static constexpr int VOLUME_NONE = -1; int getVolume() const noexcept { return vol_; } void setVolume(int volume) { vol_ = volume; } void clearVolume() noexcept { vol_ = VOLUME_NONE; } bool hasVolume() const noexcept { return vol_ != VOLUME_NONE; } static bool testEmptyVolume(int vol) { return vol == VOLUME_NONE; } static const std::string EFF_ID_NONE; // "--" std::string getEffectId(int n) const { return eff_[n].id; } void setEffectId(int n, const std::string& str) { eff_[n].id = str; } void clearEffectId(int n) { eff_[n].id = EFF_ID_NONE; } bool hasEffectId(int n) const { return eff_[n].id != EFF_ID_NONE; } static bool testEmptyEffectId(const std::string& id) { return id == EFF_ID_NONE; } static constexpr int EFF_VAL_NONE = -1; int getEffectValue(int n) const { return eff_[n].value; } void setEffectValue(int n, int v) { eff_[n].value = v; } void clearEffectValue(int n) { eff_[n].value = EFF_VAL_NONE; } bool hasEffectValue(int n) const { return eff_[n].value != EFF_VAL_NONE; } static bool testEmptyEffectValue(int v) { return v == EFF_VAL_NONE; } struct PlainEffect { std::string id; int value; }; static constexpr int N_EFFECT = 4; PlainEffect getEffect(int n) const { return eff_[n]; } void setEffect(int n, const PlainEffect& effect) { eff_[n] = effect; } void setEffect(int n, const std::string& id, int value) { setEffectId(n, id); setEffectValue(n, value); } void clearEffect(int n) { clearEffectId(n); clearEffectValue(n); } static constexpr int N_COLUMN = 3 + N_EFFECT * 2; void clear(); bool hasEvent() const; private: /// noteNum_ /// 0<=: note number (key on) /// -1: none /// -2: key off /// -3: echo previous note /// -4: echo 2 notes before /// -5: echo 3 notes before /// -6: echo 4 notes before /// -7: key cut int note_; /// instNum_ /// 0<=: instrument number /// -1: none int inst_; /// vol_ /// 0<=: volume level /// -1: none int vol_; /// eff /// [id] /// "--": none /// other: effect ID /// [value] /// 0<=: effect value /// -1: none PlainEffect eff_[N_EFFECT]; }; BambooTracker-0.6.5/BambooTracker/module/track.cpp000066400000000000000000000116131476276175200220700ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "track.hpp" #include #include #include "utils.hpp" namespace { constexpr int PATTERN_SIZE = 256; constexpr int MAX_ORDER_SIZE = 256; } Track::Track(int number, SoundSource source, int channelInSource, int defPattenSize) : effetDisplayWidth_(0), visibility_(true) { setAttribute(number, source, channelInSource); patterns_.reserve(PATTERN_SIZE); for (int i = 0; i < PATTERN_SIZE; ++i) { patterns_.emplace_back(i, defPattenSize); } patterns_[0].increaseUsedCount(); order_.push_back(0); // Set first order } void Track::setAttribute(int number, SoundSource source, int channelInSource) noexcept { attrib_.number = number; attrib_.source = source; attrib_.channelInSource = channelInSource; } OrderInfo Track::getOrderInfo(int order) const { OrderInfo info; info.trackAttribute = attrib_; info.order = order; info.patten = order_.at(static_cast(order)); return info; } size_t Track::getOrderSize() const { return order_.size(); } bool Track::canAddNewOrder() const { return order_.size() < MAX_ORDER_SIZE; } Pattern& Track::getPattern(int num) { return patterns_.at(static_cast(num)); } Pattern& Track::getPatternFromOrderNumber(int num) { return getPattern(order_.at(static_cast(num))); } int Track::searchFirstUneditedUnusedPattern() const { auto it = utils::findIf(patterns_, [](const Pattern& pattern) { return (!pattern.hasEvent() && !pattern.getUsedCount()); }); return (it == patterns_.cend() ? -1 : std::distance(patterns_.cbegin(), it)); } int Track::clonePattern(int num) { int n = searchFirstUneditedUnusedPattern(); if (n == -1) return num; else { patterns_.at(static_cast(n)) = patterns_.at(static_cast(num)).clone(n); return n; } } std::vector Track::getEditedPatternIndices() const { return utils::findIndicesIf(patterns_, [](const Pattern& pattern) { return pattern.hasEvent(); }); } std::set Track::getRegisteredInstruments() const { std::set set; for (const Pattern& pattern : patterns_) { auto&& insts = pattern.getRegisteredInstruments(); std::copy(insts.cbegin(), insts.cend(), std::inserter(set, set.end())); } return set; } void Track::registerPatternToOrder(int order, int pattern) { patterns_.at(static_cast(pattern)).increaseUsedCount(); patterns_.at(static_cast(order_.at(static_cast(order)))).decreaseUsedCount(); order_.at(static_cast(order)) = pattern; } void Track::insertOrderBelow(int order) { int n = searchFirstUneditedUnusedPattern(); if (n == -1) n = PATTERN_SIZE - 1; if (order == static_cast(order_.size()) - 1) order_.push_back(n); else order_.insert(order_.begin() + order + 1, n); patterns_[static_cast(n)].increaseUsedCount(); } void Track::deleteOrder(int order) { patterns_.at(static_cast(order_.at(static_cast(order)))).decreaseUsedCount(); order_.erase(order_.begin() + order); } void Track::swapOrder(int a, int b) { std::swap(order_.at(static_cast(a)), order_.at((static_cast(b)))); } void Track::changeDefaultPatternSize(size_t size) { for (auto& ptn : patterns_) ptn.changeSize(size); } void Track::clearUnusedPatterns() { for (Pattern& pattern : patterns_) { if (!pattern.getUsedCount() && pattern.hasEvent()) pattern.clear(); } } void Track::replaceDuplicateInstrumentsInPatterns(const std::unordered_map& map) { for (Pattern& pattern : patterns_) { if (pattern.hasEvent()) { for (size_t i = 0; i < pattern.getSize(); ++i) { Step& step = pattern.getStep(static_cast(i)); int inst = step.getInstrumentNumber(); if (map.count(inst)) step.setInstrumentNumber(map.at(inst)); } } } } void Track::transpose(int semitones, const std::vector& excludeInsts) { for (Pattern& pattern : patterns_) pattern.transpose(semitones, excludeInsts); } BambooTracker-0.6.5/BambooTracker/module/track.hpp000066400000000000000000000054251476276175200221010ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include "pattern.hpp" #include "bamboo_tracker_defs.hpp" struct TrackAttribute { int number; SoundSource source; int channelInSource; }; struct OrderInfo { TrackAttribute trackAttribute; int order; int patten; }; class Track { public: Track(int number, SoundSource source, int channelInSource, int defPattenSize); void setAttribute(int number, SoundSource source, int channelInSource) noexcept; TrackAttribute getAttribute() const noexcept { return attrib_; } OrderInfo getOrderInfo(int order) const; size_t getOrderSize() const; bool canAddNewOrder() const; Pattern& getPattern(int num); Pattern& getPatternFromOrderNumber(int num); int searchFirstUneditedUnusedPattern() const; int clonePattern(int num); std::vector getEditedPatternIndices() const; std::set getRegisteredInstruments() const; void registerPatternToOrder(int order, int pattern); void insertOrderBelow(int order); void deleteOrder(int order); void swapOrder(int a, int b); void changeDefaultPatternSize(size_t size); void setEffectDisplayWidth(size_t w) noexcept { effetDisplayWidth_ = w; } size_t getEffectDisplayWidth() const noexcept { return effetDisplayWidth_; } void setVisibility(bool visible) noexcept { visibility_ = visible; } bool isVisible() const noexcept { return visibility_; } void clearUnusedPatterns(); void replaceDuplicateInstrumentsInPatterns(const std::unordered_map& map); void transpose(int semitones, const std::vector& excludeInsts); private: TrackAttribute attrib_; std::vector order_; std::vector patterns_; size_t effetDisplayWidth_; bool visibility_; }; BambooTracker-0.6.5/BambooTracker/note.cpp000066400000000000000000002667641476276175200204670ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "note.hpp" #include #include namespace note_utils { namespace { const uint16_t centTableFM[3072] = { 0x026a, 0x026b, 0x026c, 0x026e, 0x026f, 0x0270, 0x0271, 0x0272, 0x0273, 0x0274, 0x0276, 0x0277, 0x0278, 0x0279, 0x027a, 0x027b, 0x027c, 0x027e, 0x027f, 0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0286, 0x0287, 0x0288, 0x0289, 0x028a, 0x028b, 0x028d, 0x028e, 0x028f, 0x0290, 0x0291, 0x0293, 0x0294, 0x0295, 0x0296, 0x0297, 0x0299, 0x029a, 0x029b, 0x029c, 0x029d, 0x029f, 0x02a0, 0x02a1, 0x02a2, 0x02a3, 0x02a5, 0x02a6, 0x02a7, 0x02a8, 0x02aa, 0x02ab, 0x02ac, 0x02ad, 0x02ae, 0x02b0, 0x02b1, 0x02b2, 0x02b3, 0x02b5, 0x02b6, 0x02b7, 0x02b8, 0x02ba, 0x02bb, 0x02bc, 0x02be, 0x02bf, 0x02c0, 0x02c1, 0x02c3, 0x02c4, 0x02c5, 0x02c6, 0x02c8, 0x02c9, 0x02ca, 0x02cc, 0x02cd, 0x02ce, 0x02cf, 0x02d1, 0x02d2, 0x02d3, 0x02d5, 0x02d6, 0x02d7, 0x02d9, 0x02da, 0x02db, 0x02dd, 0x02de, 0x02df, 0x02e1, 0x02e2, 0x02e3, 0x02e5, 0x02e6, 0x02e7, 0x02e9, 0x02ea, 0x02eb, 0x02ed, 0x02ee, 0x02ef, 0x02f1, 0x02f2, 0x02f3, 0x02f5, 0x02f6, 0x02f7, 0x02f9, 0x02fa, 0x02fc, 0x02fd, 0x02fe, 0x0300, 0x0301, 0x0303, 0x0304, 0x0305, 0x0307, 0x0308, 0x030a, 0x030b, 0x030c, 0x030e, 0x030f, 0x0311, 0x0312, 0x0313, 0x0315, 0x0316, 0x0318, 0x0319, 0x031b, 0x031c, 0x031d, 0x031f, 0x0320, 0x0322, 0x0323, 0x0325, 0x0326, 0x0328, 0x0329, 0x032a, 0x032c, 0x032d, 0x032f, 0x0330, 0x0332, 0x0333, 0x0335, 0x0336, 0x0338, 0x0339, 0x033b, 0x033c, 0x033e, 0x033f, 0x0341, 0x0342, 0x0344, 0x0345, 0x0347, 0x0348, 0x034a, 0x034b, 0x034d, 0x034e, 0x0350, 0x0351, 0x0353, 0x0355, 0x0356, 0x0358, 0x0359, 0x035b, 0x035c, 0x035e, 0x035f, 0x0361, 0x0362, 0x0364, 0x0366, 0x0367, 0x0369, 0x036a, 0x036c, 0x036d, 0x036f, 0x0371, 0x0372, 0x0374, 0x0375, 0x0377, 0x0379, 0x037a, 0x037c, 0x037d, 0x037f, 0x0381, 0x0382, 0x0384, 0x0386, 0x0387, 0x0389, 0x038a, 0x038c, 0x038e, 0x038f, 0x0391, 0x0393, 0x0394, 0x0396, 0x0398, 0x0399, 0x039b, 0x039d, 0x039e, 0x03a0, 0x03a2, 0x03a3, 0x03a5, 0x03a7, 0x03a8, 0x03aa, 0x03ac, 0x03ad, 0x03af, 0x03b1, 0x03b3, 0x03b4, 0x03b6, 0x03b8, 0x03b9, 0x03bb, 0x03bd, 0x03bf, 0x03c0, 0x03c2, 0x03c4, 0x03c6, 0x03c7, 0x03c9, 0x03cb, 0x03cd, 0x03ce, 0x03d0, 0x03d2, 0x03d4, 0x03d5, 0x03d7, 0x03d9, 0x03db, 0x03dd, 0x03de, 0x03e0, 0x03e2, 0x03e4, 0x03e5, 0x03e7, 0x03e9, 0x03eb, 0x03ed, 0x03ef, 0x03f0, 0x03f2, 0x03f4, 0x03f6, 0x03f8, 0x03f9, 0x03fb, 0x03fd, 0x03ff, 0x0401, 0x0403, 0x0405, 0x0406, 0x0408, 0x040a, 0x040c, 0x040e, 0x0410, 0x0412, 0x0414, 0x0415, 0x0417, 0x0419, 0x041b, 0x041d, 0x041f, 0x0421, 0x0423, 0x0425, 0x0427, 0x0428, 0x042a, 0x042c, 0x042e, 0x0430, 0x0432, 0x0434, 0x0436, 0x0438, 0x043a, 0x043c, 0x043e, 0x0440, 0x0442, 0x0444, 0x0446, 0x0448, 0x044a, 0x044c, 0x044e, 0x0450, 0x0452, 0x0454, 0x0456, 0x0458, 0x045a, 0x045c, 0x045e, 0x0460, 0x0462, 0x0464, 0x0466, 0x0468, 0x046a, 0x046c, 0x046e, 0x0470, 0x0472, 0x0474, 0x0476, 0x0478, 0x047a, 0x047c, 0x047e, 0x0480, 0x0483, 0x0485, 0x0487, 0x0489, 0x048b, 0x048d, 0x048f, 0x0491, 0x0493, 0x0495, 0x0498, 0x049a, 0x049c, 0x049e, 0x04a0, 0x04a2, 0x04a4, 0x04a6, 0x04a9, 0x04ab, 0x04ad, 0x04af, 0x04b1, 0x04b3, 0x04b6, 0x04b8, 0x04ba, 0x04bc, 0x04be, 0x04c1, 0x04c3, 0x04c5, 0x04c7, 0x04c9, 0x04cc, 0x04ce, 0x04d0, 0x04d2, 0x04d4, 0x04d7, 0x04d9, 0x04db, 0x04dd, 0x04e0, 0x04e2, 0x04e4, 0x04e6, 0x04e9, 0x04eb, 0x04ed, 0x04f0, 0x04f2, 0x04f4, 0x04f6, 0x04f9, 0x04fb, 0x04fd, 0x0500, 0x0502, 0x0504, 0x0507, 0x0509, 0x050b, 0x050e, 0x0510, 0x0512, 0x0515, 0x0517, 0x0519, 0x051c, 0x051e, 0x0520, 0x0523, 0x0525, 0x0528, 0x052a, 0x052c, 0x052f, 0x0531, 0x0533, 0x0536, 0x0538, 0x053b, 0x053d, 0x0540, 0x0542, 0x0544, 0x0547, 0x0549, 0x054c, 0x054e, 0x0551, 0x0553, 0x0556, 0x0558, 0x055a, 0x055d, 0x055f, 0x0562, 0x0564, 0x0567, 0x0569, 0x056c, 0x056e, 0x0571, 0x0573, 0x0576, 0x0578, 0x057b, 0x057e, 0x0580, 0x0583, 0x0585, 0x0588, 0x058a, 0x058d, 0x058f, 0x0592, 0x0595, 0x0597, 0x059a, 0x059c, 0x059f, 0x05a2, 0x05a4, 0x05a7, 0x05a9, 0x05ac, 0x05af, 0x05b1, 0x05b4, 0x05b6, 0x05b9, 0x05bc, 0x05be, 0x05c1, 0x05c4, 0x05c6, 0x05c9, 0x05cc, 0x05ce, 0x05d1, 0x05d4, 0x05d7, 0x05d9, 0x05dc, 0x05df, 0x05e1, 0x05e4, 0x05e7, 0x05ea, 0x05ec, 0x05ef, 0x05f2, 0x05f4, 0x05f7, 0x05fa, 0x05fd, 0x0600, 0x0602, 0x0605, 0x0608, 0x060b, 0x060d, 0x0610, 0x0613, 0x0616, 0x0619, 0x061c, 0x061e, 0x0621, 0x0624, 0x0627, 0x062a, 0x062d, 0x062f, 0x0632, 0x0635, 0x0638, 0x063b, 0x063e, 0x0641, 0x0644, 0x0646, 0x0649, 0x064c, 0x064f, 0x0652, 0x0655, 0x0658, 0x065b, 0x065e, 0x0661, 0x0664, 0x0667, 0x066a, 0x066d, 0x0670, 0x0673, 0x0675, 0x0678, 0x067b, 0x067e, 0x0681, 0x0684, 0x0687, 0x068b, 0x068e, 0x0691, 0x0694, 0x0697, 0x069a, 0x069d, 0x06a0, 0x06a3, 0x06a6, 0x06a9, 0x06ac, 0x06af, 0x06b2, 0x06b5, 0x06b8, 0x06bc, 0x06bf, 0x06c2, 0x06c5, 0x06c8, 0x06cb, 0x06ce, 0x06d1, 0x06d5, 0x06d8, 0x06db, 0x06de, 0x06e1, 0x06e5, 0x06e8, 0x06eb, 0x06ee, 0x06f1, 0x06f5, 0x06f8, 0x06fb, 0x06fe, 0x0701, 0x0705, 0x0708, 0x070b, 0x070e, 0x0712, 0x0715, 0x0718, 0x071b, 0x071f, 0x0722, 0x0725, 0x0729, 0x072c, 0x072f, 0x0733, 0x0736, 0x0739, 0x073d, 0x0740, 0x0743, 0x0747, 0x074a, 0x074d, 0x0751, 0x0754, 0x0758, 0x075b, 0x075e, 0x0762, 0x0765, 0x0769, 0x076c, 0x076f, 0x0773, 0x0776, 0x077a, 0x077d, 0x0781, 0x0784, 0x0788, 0x078b, 0x078f, 0x0792, 0x0796, 0x0799, 0x079d, 0x07a0, 0x07a4, 0x07a7, 0x07ab, 0x07ae, 0x07b2, 0x07b5, 0x07b9, 0x07bd, 0x07c0, 0x07c4, 0x07c7, 0x07cb, 0x07cf, 0x07d2, 0x07d6, 0x07d9, 0x07dd, 0x07e1, 0x07e4, 0x07e8, 0x07ec, 0x07ef, 0x07f3, 0x07f7, 0x07fa, 0x07fe, 0x0c01, 0x0c03, 0x0c05, 0x0c06, 0x0c08, 0x0c0a, 0x0c0c, 0x0c0e, 0x0c10, 0x0c12, 0x0c14, 0x0c15, 0x0c17, 0x0c19, 0x0c1b, 0x0c1d, 0x0c1f, 0x0c21, 0x0c23, 0x0c25, 0x0c27, 0x0c28, 0x0c2a, 0x0c2c, 0x0c2e, 0x0c30, 0x0c32, 0x0c34, 0x0c36, 0x0c38, 0x0c3a, 0x0c3c, 0x0c3e, 0x0c40, 0x0c42, 0x0c44, 0x0c46, 0x0c48, 0x0c4a, 0x0c4c, 0x0c4e, 0x0c50, 0x0c52, 0x0c54, 0x0c56, 0x0c58, 0x0c5a, 0x0c5c, 0x0c5e, 0x0c60, 0x0c62, 0x0c64, 0x0c66, 0x0c68, 0x0c6a, 0x0c6c, 0x0c6e, 0x0c70, 0x0c72, 0x0c74, 0x0c76, 0x0c78, 0x0c7a, 0x0c7c, 0x0c7e, 0x0c80, 0x0c83, 0x0c85, 0x0c87, 0x0c89, 0x0c8b, 0x0c8d, 0x0c8f, 0x0c91, 0x0c93, 0x0c95, 0x0c98, 0x0c9a, 0x0c9c, 0x0c9e, 0x0ca0, 0x0ca2, 0x0ca4, 0x0ca6, 0x0ca9, 0x0cab, 0x0cad, 0x0caf, 0x0cb1, 0x0cb3, 0x0cb6, 0x0cb8, 0x0cba, 0x0cbc, 0x0cbe, 0x0cc1, 0x0cc3, 0x0cc5, 0x0cc7, 0x0cc9, 0x0ccc, 0x0cce, 0x0cd0, 0x0cd2, 0x0cd4, 0x0cd7, 0x0cd9, 0x0cdb, 0x0cdd, 0x0ce0, 0x0ce2, 0x0ce4, 0x0ce6, 0x0ce9, 0x0ceb, 0x0ced, 0x0cf0, 0x0cf2, 0x0cf4, 0x0cf6, 0x0cf9, 0x0cfb, 0x0cfd, 0x0d00, 0x0d02, 0x0d04, 0x0d07, 0x0d09, 0x0d0b, 0x0d0e, 0x0d10, 0x0d12, 0x0d15, 0x0d17, 0x0d19, 0x0d1c, 0x0d1e, 0x0d20, 0x0d23, 0x0d25, 0x0d28, 0x0d2a, 0x0d2c, 0x0d2f, 0x0d31, 0x0d33, 0x0d36, 0x0d38, 0x0d3b, 0x0d3d, 0x0d40, 0x0d42, 0x0d44, 0x0d47, 0x0d49, 0x0d4c, 0x0d4e, 0x0d51, 0x0d53, 0x0d56, 0x0d58, 0x0d5a, 0x0d5d, 0x0d5f, 0x0d62, 0x0d64, 0x0d67, 0x0d69, 0x0d6c, 0x0d6e, 0x0d71, 0x0d73, 0x0d76, 0x0d78, 0x0d7b, 0x0d7e, 0x0d80, 0x0d83, 0x0d85, 0x0d88, 0x0d8a, 0x0d8d, 0x0d8f, 0x0d92, 0x0d95, 0x0d97, 0x0d9a, 0x0d9c, 0x0d9f, 0x0da2, 0x0da4, 0x0da7, 0x0da9, 0x0dac, 0x0daf, 0x0db1, 0x0db4, 0x0db6, 0x0db9, 0x0dbc, 0x0dbe, 0x0dc1, 0x0dc4, 0x0dc6, 0x0dc9, 0x0dcc, 0x0dce, 0x0dd1, 0x0dd4, 0x0dd7, 0x0dd9, 0x0ddc, 0x0ddf, 0x0de1, 0x0de4, 0x0de7, 0x0dea, 0x0dec, 0x0def, 0x0df2, 0x0df4, 0x0df7, 0x0dfa, 0x0dfd, 0x0e00, 0x0e02, 0x0e05, 0x0e08, 0x0e0b, 0x0e0d, 0x0e10, 0x0e13, 0x0e16, 0x0e19, 0x0e1c, 0x0e1e, 0x0e21, 0x0e24, 0x0e27, 0x0e2a, 0x0e2d, 0x0e2f, 0x0e32, 0x0e35, 0x0e38, 0x0e3b, 0x0e3e, 0x0e41, 0x0e44, 0x0e46, 0x0e49, 0x0e4c, 0x0e4f, 0x0e52, 0x0e55, 0x0e58, 0x0e5b, 0x0e5e, 0x0e61, 0x0e64, 0x0e67, 0x0e6a, 0x0e6d, 0x0e70, 0x0e73, 0x0e75, 0x0e78, 0x0e7b, 0x0e7e, 0x0e81, 0x0e84, 0x0e87, 0x0e8b, 0x0e8e, 0x0e91, 0x0e94, 0x0e97, 0x0e9a, 0x0e9d, 0x0ea0, 0x0ea3, 0x0ea6, 0x0ea9, 0x0eac, 0x0eaf, 0x0eb2, 0x0eb5, 0x0eb8, 0x0ebc, 0x0ebf, 0x0ec2, 0x0ec5, 0x0ec8, 0x0ecb, 0x0ece, 0x0ed1, 0x0ed5, 0x0ed8, 0x0edb, 0x0ede, 0x0ee1, 0x0ee5, 0x0ee8, 0x0eeb, 0x0eee, 0x0ef1, 0x0ef5, 0x0ef8, 0x0efb, 0x0efe, 0x0f01, 0x0f05, 0x0f08, 0x0f0b, 0x0f0e, 0x0f12, 0x0f15, 0x0f18, 0x0f1b, 0x0f1f, 0x0f22, 0x0f25, 0x0f29, 0x0f2c, 0x0f2f, 0x0f33, 0x0f36, 0x0f39, 0x0f3d, 0x0f40, 0x0f43, 0x0f47, 0x0f4a, 0x0f4d, 0x0f51, 0x0f54, 0x0f58, 0x0f5b, 0x0f5e, 0x0f62, 0x0f65, 0x0f69, 0x0f6c, 0x0f6f, 0x0f73, 0x0f76, 0x0f7a, 0x0f7d, 0x0f81, 0x0f84, 0x0f88, 0x0f8b, 0x0f8f, 0x0f92, 0x0f96, 0x0f99, 0x0f9d, 0x0fa0, 0x0fa4, 0x0fa7, 0x0fab, 0x0fae, 0x0fb2, 0x0fb5, 0x0fb9, 0x0fbd, 0x0fc0, 0x0fc4, 0x0fc7, 0x0fcb, 0x0fcf, 0x0fd2, 0x0fd6, 0x0fd9, 0x0fdd, 0x0fe1, 0x0fe4, 0x0fe8, 0x0fec, 0x0fef, 0x0ff3, 0x0ff7, 0x0ffa, 0x0ffe, 0x1401, 0x1403, 0x1405, 0x1406, 0x1408, 0x140a, 0x140c, 0x140e, 0x1410, 0x1412, 0x1414, 0x1415, 0x1417, 0x1419, 0x141b, 0x141d, 0x141f, 0x1421, 0x1423, 0x1425, 0x1427, 0x1428, 0x142a, 0x142c, 0x142e, 0x1430, 0x1432, 0x1434, 0x1436, 0x1438, 0x143a, 0x143c, 0x143e, 0x1440, 0x1442, 0x1444, 0x1446, 0x1448, 0x144a, 0x144c, 0x144e, 0x1450, 0x1452, 0x1454, 0x1456, 0x1458, 0x145a, 0x145c, 0x145e, 0x1460, 0x1462, 0x1464, 0x1466, 0x1468, 0x146a, 0x146c, 0x146e, 0x1470, 0x1472, 0x1474, 0x1476, 0x1478, 0x147a, 0x147c, 0x147e, 0x1480, 0x1483, 0x1485, 0x1487, 0x1489, 0x148b, 0x148d, 0x148f, 0x1491, 0x1493, 0x1495, 0x1498, 0x149a, 0x149c, 0x149e, 0x14a0, 0x14a2, 0x14a4, 0x14a6, 0x14a9, 0x14ab, 0x14ad, 0x14af, 0x14b1, 0x14b3, 0x14b6, 0x14b8, 0x14ba, 0x14bc, 0x14be, 0x14c1, 0x14c3, 0x14c5, 0x14c7, 0x14c9, 0x14cc, 0x14ce, 0x14d0, 0x14d2, 0x14d4, 0x14d7, 0x14d9, 0x14db, 0x14dd, 0x14e0, 0x14e2, 0x14e4, 0x14e6, 0x14e9, 0x14eb, 0x14ed, 0x14f0, 0x14f2, 0x14f4, 0x14f6, 0x14f9, 0x14fb, 0x14fd, 0x1500, 0x1502, 0x1504, 0x1507, 0x1509, 0x150b, 0x150e, 0x1510, 0x1512, 0x1515, 0x1517, 0x1519, 0x151c, 0x151e, 0x1520, 0x1523, 0x1525, 0x1528, 0x152a, 0x152c, 0x152f, 0x1531, 0x1533, 0x1536, 0x1538, 0x153b, 0x153d, 0x1540, 0x1542, 0x1544, 0x1547, 0x1549, 0x154c, 0x154e, 0x1551, 0x1553, 0x1556, 0x1558, 0x155a, 0x155d, 0x155f, 0x1562, 0x1564, 0x1567, 0x1569, 0x156c, 0x156e, 0x1571, 0x1573, 0x1576, 0x1578, 0x157b, 0x157e, 0x1580, 0x1583, 0x1585, 0x1588, 0x158a, 0x158d, 0x158f, 0x1592, 0x1595, 0x1597, 0x159a, 0x159c, 0x159f, 0x15a2, 0x15a4, 0x15a7, 0x15a9, 0x15ac, 0x15af, 0x15b1, 0x15b4, 0x15b6, 0x15b9, 0x15bc, 0x15be, 0x15c1, 0x15c4, 0x15c6, 0x15c9, 0x15cc, 0x15ce, 0x15d1, 0x15d4, 0x15d7, 0x15d9, 0x15dc, 0x15df, 0x15e1, 0x15e4, 0x15e7, 0x15ea, 0x15ec, 0x15ef, 0x15f2, 0x15f4, 0x15f7, 0x15fa, 0x15fd, 0x1600, 0x1602, 0x1605, 0x1608, 0x160b, 0x160d, 0x1610, 0x1613, 0x1616, 0x1619, 0x161c, 0x161e, 0x1621, 0x1624, 0x1627, 0x162a, 0x162d, 0x162f, 0x1632, 0x1635, 0x1638, 0x163b, 0x163e, 0x1641, 0x1644, 0x1646, 0x1649, 0x164c, 0x164f, 0x1652, 0x1655, 0x1658, 0x165b, 0x165e, 0x1661, 0x1664, 0x1667, 0x166a, 0x166d, 0x1670, 0x1673, 0x1675, 0x1678, 0x167b, 0x167e, 0x1681, 0x1684, 0x1687, 0x168b, 0x168e, 0x1691, 0x1694, 0x1697, 0x169a, 0x169d, 0x16a0, 0x16a3, 0x16a6, 0x16a9, 0x16ac, 0x16af, 0x16b2, 0x16b5, 0x16b8, 0x16bc, 0x16bf, 0x16c2, 0x16c5, 0x16c8, 0x16cb, 0x16ce, 0x16d1, 0x16d5, 0x16d8, 0x16db, 0x16de, 0x16e1, 0x16e5, 0x16e8, 0x16eb, 0x16ee, 0x16f1, 0x16f5, 0x16f8, 0x16fb, 0x16fe, 0x1701, 0x1705, 0x1708, 0x170b, 0x170e, 0x1712, 0x1715, 0x1718, 0x171b, 0x171f, 0x1722, 0x1725, 0x1729, 0x172c, 0x172f, 0x1733, 0x1736, 0x1739, 0x173d, 0x1740, 0x1743, 0x1747, 0x174a, 0x174d, 0x1751, 0x1754, 0x1758, 0x175b, 0x175e, 0x1762, 0x1765, 0x1769, 0x176c, 0x176f, 0x1773, 0x1776, 0x177a, 0x177d, 0x1781, 0x1784, 0x1788, 0x178b, 0x178f, 0x1792, 0x1796, 0x1799, 0x179d, 0x17a0, 0x17a4, 0x17a7, 0x17ab, 0x17ae, 0x17b2, 0x17b5, 0x17b9, 0x17bd, 0x17c0, 0x17c4, 0x17c7, 0x17cb, 0x17cf, 0x17d2, 0x17d6, 0x17d9, 0x17dd, 0x17e1, 0x17e4, 0x17e8, 0x17ec, 0x17ef, 0x17f3, 0x17f7, 0x17fa, 0x17fe, 0x1c01, 0x1c03, 0x1c05, 0x1c06, 0x1c08, 0x1c0a, 0x1c0c, 0x1c0e, 0x1c10, 0x1c12, 0x1c14, 0x1c15, 0x1c17, 0x1c19, 0x1c1b, 0x1c1d, 0x1c1f, 0x1c21, 0x1c23, 0x1c25, 0x1c27, 0x1c28, 0x1c2a, 0x1c2c, 0x1c2e, 0x1c30, 0x1c32, 0x1c34, 0x1c36, 0x1c38, 0x1c3a, 0x1c3c, 0x1c3e, 0x1c40, 0x1c42, 0x1c44, 0x1c46, 0x1c48, 0x1c4a, 0x1c4c, 0x1c4e, 0x1c50, 0x1c52, 0x1c54, 0x1c56, 0x1c58, 0x1c5a, 0x1c5c, 0x1c5e, 0x1c60, 0x1c62, 0x1c64, 0x1c66, 0x1c68, 0x1c6a, 0x1c6c, 0x1c6e, 0x1c70, 0x1c72, 0x1c74, 0x1c76, 0x1c78, 0x1c7a, 0x1c7c, 0x1c7e, 0x1c80, 0x1c83, 0x1c85, 0x1c87, 0x1c89, 0x1c8b, 0x1c8d, 0x1c8f, 0x1c91, 0x1c93, 0x1c95, 0x1c98, 0x1c9a, 0x1c9c, 0x1c9e, 0x1ca0, 0x1ca2, 0x1ca4, 0x1ca6, 0x1ca9, 0x1cab, 0x1cad, 0x1caf, 0x1cb1, 0x1cb3, 0x1cb6, 0x1cb8, 0x1cba, 0x1cbc, 0x1cbe, 0x1cc1, 0x1cc3, 0x1cc5, 0x1cc7, 0x1cc9, 0x1ccc, 0x1cce, 0x1cd0, 0x1cd2, 0x1cd4, 0x1cd7, 0x1cd9, 0x1cdb, 0x1cdd, 0x1ce0, 0x1ce2, 0x1ce4, 0x1ce6, 0x1ce9, 0x1ceb, 0x1ced, 0x1cf0, 0x1cf2, 0x1cf4, 0x1cf6, 0x1cf9, 0x1cfb, 0x1cfd, 0x1d00, 0x1d02, 0x1d04, 0x1d07, 0x1d09, 0x1d0b, 0x1d0e, 0x1d10, 0x1d12, 0x1d15, 0x1d17, 0x1d19, 0x1d1c, 0x1d1e, 0x1d20, 0x1d23, 0x1d25, 0x1d28, 0x1d2a, 0x1d2c, 0x1d2f, 0x1d31, 0x1d33, 0x1d36, 0x1d38, 0x1d3b, 0x1d3d, 0x1d40, 0x1d42, 0x1d44, 0x1d47, 0x1d49, 0x1d4c, 0x1d4e, 0x1d51, 0x1d53, 0x1d56, 0x1d58, 0x1d5a, 0x1d5d, 0x1d5f, 0x1d62, 0x1d64, 0x1d67, 0x1d69, 0x1d6c, 0x1d6e, 0x1d71, 0x1d73, 0x1d76, 0x1d78, 0x1d7b, 0x1d7e, 0x1d80, 0x1d83, 0x1d85, 0x1d88, 0x1d8a, 0x1d8d, 0x1d8f, 0x1d92, 0x1d95, 0x1d97, 0x1d9a, 0x1d9c, 0x1d9f, 0x1da2, 0x1da4, 0x1da7, 0x1da9, 0x1dac, 0x1daf, 0x1db1, 0x1db4, 0x1db6, 0x1db9, 0x1dbc, 0x1dbe, 0x1dc1, 0x1dc4, 0x1dc6, 0x1dc9, 0x1dcc, 0x1dce, 0x1dd1, 0x1dd4, 0x1dd7, 0x1dd9, 0x1ddc, 0x1ddf, 0x1de1, 0x1de4, 0x1de7, 0x1dea, 0x1dec, 0x1def, 0x1df2, 0x1df4, 0x1df7, 0x1dfa, 0x1dfd, 0x1e00, 0x1e02, 0x1e05, 0x1e08, 0x1e0b, 0x1e0d, 0x1e10, 0x1e13, 0x1e16, 0x1e19, 0x1e1c, 0x1e1e, 0x1e21, 0x1e24, 0x1e27, 0x1e2a, 0x1e2d, 0x1e2f, 0x1e32, 0x1e35, 0x1e38, 0x1e3b, 0x1e3e, 0x1e41, 0x1e44, 0x1e46, 0x1e49, 0x1e4c, 0x1e4f, 0x1e52, 0x1e55, 0x1e58, 0x1e5b, 0x1e5e, 0x1e61, 0x1e64, 0x1e67, 0x1e6a, 0x1e6d, 0x1e70, 0x1e73, 0x1e75, 0x1e78, 0x1e7b, 0x1e7e, 0x1e81, 0x1e84, 0x1e87, 0x1e8b, 0x1e8e, 0x1e91, 0x1e94, 0x1e97, 0x1e9a, 0x1e9d, 0x1ea0, 0x1ea3, 0x1ea6, 0x1ea9, 0x1eac, 0x1eaf, 0x1eb2, 0x1eb5, 0x1eb8, 0x1ebc, 0x1ebf, 0x1ec2, 0x1ec5, 0x1ec8, 0x1ecb, 0x1ece, 0x1ed1, 0x1ed5, 0x1ed8, 0x1edb, 0x1ede, 0x1ee1, 0x1ee5, 0x1ee8, 0x1eeb, 0x1eee, 0x1ef1, 0x1ef5, 0x1ef8, 0x1efb, 0x1efe, 0x1f01, 0x1f05, 0x1f08, 0x1f0b, 0x1f0e, 0x1f12, 0x1f15, 0x1f18, 0x1f1b, 0x1f1f, 0x1f22, 0x1f25, 0x1f29, 0x1f2c, 0x1f2f, 0x1f33, 0x1f36, 0x1f39, 0x1f3d, 0x1f40, 0x1f43, 0x1f47, 0x1f4a, 0x1f4d, 0x1f51, 0x1f54, 0x1f58, 0x1f5b, 0x1f5e, 0x1f62, 0x1f65, 0x1f69, 0x1f6c, 0x1f6f, 0x1f73, 0x1f76, 0x1f7a, 0x1f7d, 0x1f81, 0x1f84, 0x1f88, 0x1f8b, 0x1f8f, 0x1f92, 0x1f96, 0x1f99, 0x1f9d, 0x1fa0, 0x1fa4, 0x1fa7, 0x1fab, 0x1fae, 0x1fb2, 0x1fb5, 0x1fb9, 0x1fbd, 0x1fc0, 0x1fc4, 0x1fc7, 0x1fcb, 0x1fcf, 0x1fd2, 0x1fd6, 0x1fd9, 0x1fdd, 0x1fe1, 0x1fe4, 0x1fe8, 0x1fec, 0x1fef, 0x1ff3, 0x1ff7, 0x1ffa, 0x1ffe, 0x2401, 0x2403, 0x2405, 0x2406, 0x2408, 0x240a, 0x240c, 0x240e, 0x2410, 0x2412, 0x2414, 0x2415, 0x2417, 0x2419, 0x241b, 0x241d, 0x241f, 0x2421, 0x2423, 0x2425, 0x2427, 0x2428, 0x242a, 0x242c, 0x242e, 0x2430, 0x2432, 0x2434, 0x2436, 0x2438, 0x243a, 0x243c, 0x243e, 0x2440, 0x2442, 0x2444, 0x2446, 0x2448, 0x244a, 0x244c, 0x244e, 0x2450, 0x2452, 0x2454, 0x2456, 0x2458, 0x245a, 0x245c, 0x245e, 0x2460, 0x2462, 0x2464, 0x2466, 0x2468, 0x246a, 0x246c, 0x246e, 0x2470, 0x2472, 0x2474, 0x2476, 0x2478, 0x247a, 0x247c, 0x247e, 0x2480, 0x2483, 0x2485, 0x2487, 0x2489, 0x248b, 0x248d, 0x248f, 0x2491, 0x2493, 0x2495, 0x2498, 0x249a, 0x249c, 0x249e, 0x24a0, 0x24a2, 0x24a4, 0x24a6, 0x24a9, 0x24ab, 0x24ad, 0x24af, 0x24b1, 0x24b3, 0x24b6, 0x24b8, 0x24ba, 0x24bc, 0x24be, 0x24c1, 0x24c3, 0x24c5, 0x24c7, 0x24c9, 0x24cc, 0x24ce, 0x24d0, 0x24d2, 0x24d4, 0x24d7, 0x24d9, 0x24db, 0x24dd, 0x24e0, 0x24e2, 0x24e4, 0x24e6, 0x24e9, 0x24eb, 0x24ed, 0x24f0, 0x24f2, 0x24f4, 0x24f6, 0x24f9, 0x24fb, 0x24fd, 0x2500, 0x2502, 0x2504, 0x2507, 0x2509, 0x250b, 0x250e, 0x2510, 0x2512, 0x2515, 0x2517, 0x2519, 0x251c, 0x251e, 0x2520, 0x2523, 0x2525, 0x2528, 0x252a, 0x252c, 0x252f, 0x2531, 0x2533, 0x2536, 0x2538, 0x253b, 0x253d, 0x2540, 0x2542, 0x2544, 0x2547, 0x2549, 0x254c, 0x254e, 0x2551, 0x2553, 0x2556, 0x2558, 0x255a, 0x255d, 0x255f, 0x2562, 0x2564, 0x2567, 0x2569, 0x256c, 0x256e, 0x2571, 0x2573, 0x2576, 0x2578, 0x257b, 0x257e, 0x2580, 0x2583, 0x2585, 0x2588, 0x258a, 0x258d, 0x258f, 0x2592, 0x2595, 0x2597, 0x259a, 0x259c, 0x259f, 0x25a2, 0x25a4, 0x25a7, 0x25a9, 0x25ac, 0x25af, 0x25b1, 0x25b4, 0x25b6, 0x25b9, 0x25bc, 0x25be, 0x25c1, 0x25c4, 0x25c6, 0x25c9, 0x25cc, 0x25ce, 0x25d1, 0x25d4, 0x25d7, 0x25d9, 0x25dc, 0x25df, 0x25e1, 0x25e4, 0x25e7, 0x25ea, 0x25ec, 0x25ef, 0x25f2, 0x25f4, 0x25f7, 0x25fa, 0x25fd, 0x2600, 0x2602, 0x2605, 0x2608, 0x260b, 0x260d, 0x2610, 0x2613, 0x2616, 0x2619, 0x261c, 0x261e, 0x2621, 0x2624, 0x2627, 0x262a, 0x262d, 0x262f, 0x2632, 0x2635, 0x2638, 0x263b, 0x263e, 0x2641, 0x2644, 0x2646, 0x2649, 0x264c, 0x264f, 0x2652, 0x2655, 0x2658, 0x265b, 0x265e, 0x2661, 0x2664, 0x2667, 0x266a, 0x266d, 0x2670, 0x2673, 0x2675, 0x2678, 0x267b, 0x267e, 0x2681, 0x2684, 0x2687, 0x268b, 0x268e, 0x2691, 0x2694, 0x2697, 0x269a, 0x269d, 0x26a0, 0x26a3, 0x26a6, 0x26a9, 0x26ac, 0x26af, 0x26b2, 0x26b5, 0x26b8, 0x26bc, 0x26bf, 0x26c2, 0x26c5, 0x26c8, 0x26cb, 0x26ce, 0x26d1, 0x26d5, 0x26d8, 0x26db, 0x26de, 0x26e1, 0x26e5, 0x26e8, 0x26eb, 0x26ee, 0x26f1, 0x26f5, 0x26f8, 0x26fb, 0x26fe, 0x2701, 0x2705, 0x2708, 0x270b, 0x270e, 0x2712, 0x2715, 0x2718, 0x271b, 0x271f, 0x2722, 0x2725, 0x2729, 0x272c, 0x272f, 0x2733, 0x2736, 0x2739, 0x273d, 0x2740, 0x2743, 0x2747, 0x274a, 0x274d, 0x2751, 0x2754, 0x2758, 0x275b, 0x275e, 0x2762, 0x2765, 0x2769, 0x276c, 0x276f, 0x2773, 0x2776, 0x277a, 0x277d, 0x2781, 0x2784, 0x2788, 0x278b, 0x278f, 0x2792, 0x2796, 0x2799, 0x279d, 0x27a0, 0x27a4, 0x27a7, 0x27ab, 0x27ae, 0x27b2, 0x27b5, 0x27b9, 0x27bd, 0x27c0, 0x27c4, 0x27c7, 0x27cb, 0x27cf, 0x27d2, 0x27d6, 0x27d9, 0x27dd, 0x27e1, 0x27e4, 0x27e8, 0x27ec, 0x27ef, 0x27f3, 0x27f7, 0x27fa, 0x27fe, 0x2c01, 0x2c03, 0x2c05, 0x2c06, 0x2c08, 0x2c0a, 0x2c0c, 0x2c0e, 0x2c10, 0x2c12, 0x2c14, 0x2c15, 0x2c17, 0x2c19, 0x2c1b, 0x2c1d, 0x2c1f, 0x2c21, 0x2c23, 0x2c25, 0x2c27, 0x2c28, 0x2c2a, 0x2c2c, 0x2c2e, 0x2c30, 0x2c32, 0x2c34, 0x2c36, 0x2c38, 0x2c3a, 0x2c3c, 0x2c3e, 0x2c40, 0x2c42, 0x2c44, 0x2c46, 0x2c48, 0x2c4a, 0x2c4c, 0x2c4e, 0x2c50, 0x2c52, 0x2c54, 0x2c56, 0x2c58, 0x2c5a, 0x2c5c, 0x2c5e, 0x2c60, 0x2c62, 0x2c64, 0x2c66, 0x2c68, 0x2c6a, 0x2c6c, 0x2c6e, 0x2c70, 0x2c72, 0x2c74, 0x2c76, 0x2c78, 0x2c7a, 0x2c7c, 0x2c7e, 0x2c80, 0x2c83, 0x2c85, 0x2c87, 0x2c89, 0x2c8b, 0x2c8d, 0x2c8f, 0x2c91, 0x2c93, 0x2c95, 0x2c98, 0x2c9a, 0x2c9c, 0x2c9e, 0x2ca0, 0x2ca2, 0x2ca4, 0x2ca6, 0x2ca9, 0x2cab, 0x2cad, 0x2caf, 0x2cb1, 0x2cb3, 0x2cb6, 0x2cb8, 0x2cba, 0x2cbc, 0x2cbe, 0x2cc1, 0x2cc3, 0x2cc5, 0x2cc7, 0x2cc9, 0x2ccc, 0x2cce, 0x2cd0, 0x2cd2, 0x2cd4, 0x2cd7, 0x2cd9, 0x2cdb, 0x2cdd, 0x2ce0, 0x2ce2, 0x2ce4, 0x2ce6, 0x2ce9, 0x2ceb, 0x2ced, 0x2cf0, 0x2cf2, 0x2cf4, 0x2cf6, 0x2cf9, 0x2cfb, 0x2cfd, 0x2d00, 0x2d02, 0x2d04, 0x2d07, 0x2d09, 0x2d0b, 0x2d0e, 0x2d10, 0x2d12, 0x2d15, 0x2d17, 0x2d19, 0x2d1c, 0x2d1e, 0x2d20, 0x2d23, 0x2d25, 0x2d28, 0x2d2a, 0x2d2c, 0x2d2f, 0x2d31, 0x2d33, 0x2d36, 0x2d38, 0x2d3b, 0x2d3d, 0x2d40, 0x2d42, 0x2d44, 0x2d47, 0x2d49, 0x2d4c, 0x2d4e, 0x2d51, 0x2d53, 0x2d56, 0x2d58, 0x2d5a, 0x2d5d, 0x2d5f, 0x2d62, 0x2d64, 0x2d67, 0x2d69, 0x2d6c, 0x2d6e, 0x2d71, 0x2d73, 0x2d76, 0x2d78, 0x2d7b, 0x2d7e, 0x2d80, 0x2d83, 0x2d85, 0x2d88, 0x2d8a, 0x2d8d, 0x2d8f, 0x2d92, 0x2d95, 0x2d97, 0x2d9a, 0x2d9c, 0x2d9f, 0x2da2, 0x2da4, 0x2da7, 0x2da9, 0x2dac, 0x2daf, 0x2db1, 0x2db4, 0x2db6, 0x2db9, 0x2dbc, 0x2dbe, 0x2dc1, 0x2dc4, 0x2dc6, 0x2dc9, 0x2dcc, 0x2dce, 0x2dd1, 0x2dd4, 0x2dd7, 0x2dd9, 0x2ddc, 0x2ddf, 0x2de1, 0x2de4, 0x2de7, 0x2dea, 0x2dec, 0x2def, 0x2df2, 0x2df4, 0x2df7, 0x2dfa, 0x2dfd, 0x2e00, 0x2e02, 0x2e05, 0x2e08, 0x2e0b, 0x2e0d, 0x2e10, 0x2e13, 0x2e16, 0x2e19, 0x2e1c, 0x2e1e, 0x2e21, 0x2e24, 0x2e27, 0x2e2a, 0x2e2d, 0x2e2f, 0x2e32, 0x2e35, 0x2e38, 0x2e3b, 0x2e3e, 0x2e41, 0x2e44, 0x2e46, 0x2e49, 0x2e4c, 0x2e4f, 0x2e52, 0x2e55, 0x2e58, 0x2e5b, 0x2e5e, 0x2e61, 0x2e64, 0x2e67, 0x2e6a, 0x2e6d, 0x2e70, 0x2e73, 0x2e75, 0x2e78, 0x2e7b, 0x2e7e, 0x2e81, 0x2e84, 0x2e87, 0x2e8b, 0x2e8e, 0x2e91, 0x2e94, 0x2e97, 0x2e9a, 0x2e9d, 0x2ea0, 0x2ea3, 0x2ea6, 0x2ea9, 0x2eac, 0x2eaf, 0x2eb2, 0x2eb5, 0x2eb8, 0x2ebc, 0x2ebf, 0x2ec2, 0x2ec5, 0x2ec8, 0x2ecb, 0x2ece, 0x2ed1, 0x2ed5, 0x2ed8, 0x2edb, 0x2ede, 0x2ee1, 0x2ee5, 0x2ee8, 0x2eeb, 0x2eee, 0x2ef1, 0x2ef5, 0x2ef8, 0x2efb, 0x2efe, 0x2f01, 0x2f05, 0x2f08, 0x2f0b, 0x2f0e, 0x2f12, 0x2f15, 0x2f18, 0x2f1b, 0x2f1f, 0x2f22, 0x2f25, 0x2f29, 0x2f2c, 0x2f2f, 0x2f33, 0x2f36, 0x2f39, 0x2f3d, 0x2f40, 0x2f43, 0x2f47, 0x2f4a, 0x2f4d, 0x2f51, 0x2f54, 0x2f58, 0x2f5b, 0x2f5e, 0x2f62, 0x2f65, 0x2f69, 0x2f6c, 0x2f6f, 0x2f73, 0x2f76, 0x2f7a, 0x2f7d, 0x2f81, 0x2f84, 0x2f88, 0x2f8b, 0x2f8f, 0x2f92, 0x2f96, 0x2f99, 0x2f9d, 0x2fa0, 0x2fa4, 0x2fa7, 0x2fab, 0x2fae, 0x2fb2, 0x2fb5, 0x2fb9, 0x2fbd, 0x2fc0, 0x2fc4, 0x2fc7, 0x2fcb, 0x2fcf, 0x2fd2, 0x2fd6, 0x2fd9, 0x2fdd, 0x2fe1, 0x2fe4, 0x2fe8, 0x2fec, 0x2fef, 0x2ff3, 0x2ff7, 0x2ffa, 0x2ffe, 0x3401, 0x3403, 0x3405, 0x3406, 0x3408, 0x340a, 0x340c, 0x340e, 0x3410, 0x3412, 0x3414, 0x3415, 0x3417, 0x3419, 0x341b, 0x341d, 0x341f, 0x3421, 0x3423, 0x3425, 0x3427, 0x3428, 0x342a, 0x342c, 0x342e, 0x3430, 0x3432, 0x3434, 0x3436, 0x3438, 0x343a, 0x343c, 0x343e, 0x3440, 0x3442, 0x3444, 0x3446, 0x3448, 0x344a, 0x344c, 0x344e, 0x3450, 0x3452, 0x3454, 0x3456, 0x3458, 0x345a, 0x345c, 0x345e, 0x3460, 0x3462, 0x3464, 0x3466, 0x3468, 0x346a, 0x346c, 0x346e, 0x3470, 0x3472, 0x3474, 0x3476, 0x3478, 0x347a, 0x347c, 0x347e, 0x3480, 0x3483, 0x3485, 0x3487, 0x3489, 0x348b, 0x348d, 0x348f, 0x3491, 0x3493, 0x3495, 0x3498, 0x349a, 0x349c, 0x349e, 0x34a0, 0x34a2, 0x34a4, 0x34a6, 0x34a9, 0x34ab, 0x34ad, 0x34af, 0x34b1, 0x34b3, 0x34b6, 0x34b8, 0x34ba, 0x34bc, 0x34be, 0x34c1, 0x34c3, 0x34c5, 0x34c7, 0x34c9, 0x34cc, 0x34ce, 0x34d0, 0x34d2, 0x34d4, 0x34d7, 0x34d9, 0x34db, 0x34dd, 0x34e0, 0x34e2, 0x34e4, 0x34e6, 0x34e9, 0x34eb, 0x34ed, 0x34f0, 0x34f2, 0x34f4, 0x34f6, 0x34f9, 0x34fb, 0x34fd, 0x3500, 0x3502, 0x3504, 0x3507, 0x3509, 0x350b, 0x350e, 0x3510, 0x3512, 0x3515, 0x3517, 0x3519, 0x351c, 0x351e, 0x3520, 0x3523, 0x3525, 0x3528, 0x352a, 0x352c, 0x352f, 0x3531, 0x3533, 0x3536, 0x3538, 0x353b, 0x353d, 0x3540, 0x3542, 0x3544, 0x3547, 0x3549, 0x354c, 0x354e, 0x3551, 0x3553, 0x3556, 0x3558, 0x355a, 0x355d, 0x355f, 0x3562, 0x3564, 0x3567, 0x3569, 0x356c, 0x356e, 0x3571, 0x3573, 0x3576, 0x3578, 0x357b, 0x357e, 0x3580, 0x3583, 0x3585, 0x3588, 0x358a, 0x358d, 0x358f, 0x3592, 0x3595, 0x3597, 0x359a, 0x359c, 0x359f, 0x35a2, 0x35a4, 0x35a7, 0x35a9, 0x35ac, 0x35af, 0x35b1, 0x35b4, 0x35b6, 0x35b9, 0x35bc, 0x35be, 0x35c1, 0x35c4, 0x35c6, 0x35c9, 0x35cc, 0x35ce, 0x35d1, 0x35d4, 0x35d7, 0x35d9, 0x35dc, 0x35df, 0x35e1, 0x35e4, 0x35e7, 0x35ea, 0x35ec, 0x35ef, 0x35f2, 0x35f4, 0x35f7, 0x35fa, 0x35fd, 0x3600, 0x3602, 0x3605, 0x3608, 0x360b, 0x360d, 0x3610, 0x3613, 0x3616, 0x3619, 0x361c, 0x361e, 0x3621, 0x3624, 0x3627, 0x362a, 0x362d, 0x362f, 0x3632, 0x3635, 0x3638, 0x363b, 0x363e, 0x3641, 0x3644, 0x3646, 0x3649, 0x364c, 0x364f, 0x3652, 0x3655, 0x3658, 0x365b, 0x365e, 0x3661, 0x3664, 0x3667, 0x366a, 0x366d, 0x3670, 0x3673, 0x3675, 0x3678, 0x367b, 0x367e, 0x3681, 0x3684, 0x3687, 0x368b, 0x368e, 0x3691, 0x3694, 0x3697, 0x369a, 0x369d, 0x36a0, 0x36a3, 0x36a6, 0x36a9, 0x36ac, 0x36af, 0x36b2, 0x36b5, 0x36b8, 0x36bc, 0x36bf, 0x36c2, 0x36c5, 0x36c8, 0x36cb, 0x36ce, 0x36d1, 0x36d5, 0x36d8, 0x36db, 0x36de, 0x36e1, 0x36e5, 0x36e8, 0x36eb, 0x36ee, 0x36f1, 0x36f5, 0x36f8, 0x36fb, 0x36fe, 0x3701, 0x3705, 0x3708, 0x370b, 0x370e, 0x3712, 0x3715, 0x3718, 0x371b, 0x371f, 0x3722, 0x3725, 0x3729, 0x372c, 0x372f, 0x3733, 0x3736, 0x3739, 0x373d, 0x3740, 0x3743, 0x3747, 0x374a, 0x374d, 0x3751, 0x3754, 0x3758, 0x375b, 0x375e, 0x3762, 0x3765, 0x3769, 0x376c, 0x376f, 0x3773, 0x3776, 0x377a, 0x377d, 0x3781, 0x3784, 0x3788, 0x378b, 0x378f, 0x3792, 0x3796, 0x3799, 0x379d, 0x37a0, 0x37a4, 0x37a7, 0x37ab, 0x37ae, 0x37b2, 0x37b5, 0x37b9, 0x37bd, 0x37c0, 0x37c4, 0x37c7, 0x37cb, 0x37cf, 0x37d2, 0x37d6, 0x37d9, 0x37dd, 0x37e1, 0x37e4, 0x37e8, 0x37ec, 0x37ef, 0x37f3, 0x37f7, 0x37fa, 0x37fe, 0x3c01, 0x3c03, 0x3c05, 0x3c06, 0x3c08, 0x3c0a, 0x3c0c, 0x3c0e, 0x3c10, 0x3c12, 0x3c14, 0x3c15, 0x3c17, 0x3c19, 0x3c1b, 0x3c1d, 0x3c1f, 0x3c21, 0x3c23, 0x3c25, 0x3c27, 0x3c28, 0x3c2a, 0x3c2c, 0x3c2e, 0x3c30, 0x3c32, 0x3c34, 0x3c36, 0x3c38, 0x3c3a, 0x3c3c, 0x3c3e, 0x3c40, 0x3c42, 0x3c44, 0x3c46, 0x3c48, 0x3c4a, 0x3c4c, 0x3c4e, 0x3c50, 0x3c52, 0x3c54, 0x3c56, 0x3c58, 0x3c5a, 0x3c5c, 0x3c5e, 0x3c60, 0x3c62, 0x3c64, 0x3c66, 0x3c68, 0x3c6a, 0x3c6c, 0x3c6e, 0x3c70, 0x3c72, 0x3c74, 0x3c76, 0x3c78, 0x3c7a, 0x3c7c, 0x3c7e, 0x3c80, 0x3c83, 0x3c85, 0x3c87, 0x3c89, 0x3c8b, 0x3c8d, 0x3c8f, 0x3c91, 0x3c93, 0x3c95, 0x3c98, 0x3c9a, 0x3c9c, 0x3c9e, 0x3ca0, 0x3ca2, 0x3ca4, 0x3ca6, 0x3ca9, 0x3cab, 0x3cad, 0x3caf, 0x3cb1, 0x3cb3, 0x3cb6, 0x3cb8, 0x3cba, 0x3cbc, 0x3cbe, 0x3cc1, 0x3cc3, 0x3cc5, 0x3cc7, 0x3cc9, 0x3ccc, 0x3cce, 0x3cd0, 0x3cd2 }; static_assert(sizeof(centTableFM) == sizeof(uint16_t) * Note::ABS_PITCH_RANGE, "Invalid FM cent table size."); const uint16_t centTableSSGSquare[3072] = { 0xee8, 0xee1, 0xeda, 0xed4, 0xecd, 0xec6, 0xebf, 0xeb8, 0xeb1, 0xeab, 0xea4, 0xe9d, 0xe96, 0xe90, 0xe89, 0xe82, 0xe7c, 0xe75, 0xe6e, 0xe67, 0xe61, 0xe5a, 0xe54, 0xe4d, 0xe46, 0xe40, 0xe39, 0xe33, 0xe2c, 0xe26, 0xe1f, 0xe18, 0xe12, 0xe0b, 0xe05, 0xdff, 0xdf8, 0xdf2, 0xdeb, 0xde5, 0xdde, 0xdd8, 0xdd2, 0xdcb, 0xdc5, 0xdbe, 0xdb8, 0xdb2, 0xdab, 0xda5, 0xd9f, 0xd99, 0xd92, 0xd8c, 0xd86, 0xd7f, 0xd79, 0xd73, 0xd6d, 0xd67, 0xd60, 0xd5a, 0xd54, 0xd4e, 0xd48, 0xd42, 0xd3c, 0xd35, 0xd2f, 0xd29, 0xd23, 0xd1d, 0xd17, 0xd11, 0xd0b, 0xd05, 0xcff, 0xcf9, 0xcf3, 0xced, 0xce7, 0xce1, 0xcdb, 0xcd5, 0xccf, 0xcc9, 0xcc3, 0xcbe, 0xcb8, 0xcb2, 0xcac, 0xca6, 0xca0, 0xc9a, 0xc95, 0xc8f, 0xc89, 0xc83, 0xc7d, 0xc78, 0xc72, 0xc6c, 0xc66, 0xc61, 0xc5b, 0xc55, 0xc50, 0xc4a, 0xc44, 0xc3f, 0xc39, 0xc33, 0xc2e, 0xc28, 0xc22, 0xc1d, 0xc17, 0xc12, 0xc0c, 0xc06, 0xc01, 0xbfb, 0xbf6, 0xbf0, 0xbeb, 0xbe5, 0xbe0, 0xbda, 0xbd5, 0xbcf, 0xbca, 0xbc5, 0xbbf, 0xbba, 0xbb4, 0xbaf, 0xba9, 0xba4, 0xb9f, 0xb99, 0xb94, 0xb8f, 0xb89, 0xb84, 0xb7f, 0xb79, 0xb74, 0xb6f, 0xb69, 0xb64, 0xb5f, 0xb5a, 0xb54, 0xb4f, 0xb4a, 0xb45, 0xb40, 0xb3a, 0xb35, 0xb30, 0xb2b, 0xb26, 0xb21, 0xb1b, 0xb16, 0xb11, 0xb0c, 0xb07, 0xb02, 0xafd, 0xaf8, 0xaf3, 0xaee, 0xae9, 0xae4, 0xadf, 0xad9, 0xad4, 0xacf, 0xaca, 0xac6, 0xac1, 0xabc, 0xab7, 0xab2, 0xaad, 0xaa8, 0xaa3, 0xa9e, 0xa99, 0xa94, 0xa8f, 0xa8a, 0xa86, 0xa81, 0xa7c, 0xa77, 0xa72, 0xa6d, 0xa69, 0xa64, 0xa5f, 0xa5a, 0xa55, 0xa51, 0xa4c, 0xa47, 0xa42, 0xa3e, 0xa39, 0xa34, 0xa2f, 0xa2b, 0xa26, 0xa21, 0xa1d, 0xa18, 0xa13, 0xa0f, 0xa0a, 0xa05, 0xa01, 0x9fc, 0x9f8, 0x9f3, 0x9ee, 0x9ea, 0x9e5, 0x9e1, 0x9dc, 0x9d8, 0x9d3, 0x9ce, 0x9ca, 0x9c5, 0x9c1, 0x9bc, 0x9b8, 0x9b3, 0x9af, 0x9aa, 0x9a6, 0x9a2, 0x99d, 0x999, 0x994, 0x990, 0x98b, 0x987, 0x983, 0x97e, 0x97a, 0x975, 0x971, 0x96d, 0x968, 0x964, 0x960, 0x95b, 0x957, 0x953, 0x94e, 0x94a, 0x946, 0x942, 0x93d, 0x939, 0x935, 0x931, 0x92c, 0x928, 0x924, 0x920, 0x91b, 0x917, 0x913, 0x90f, 0x90b, 0x906, 0x902, 0x8fe, 0x8fa, 0x8f6, 0x8f2, 0x8ee, 0x8e9, 0x8e5, 0x8e1, 0x8dd, 0x8d9, 0x8d5, 0x8d1, 0x8cd, 0x8c9, 0x8c5, 0x8c1, 0x8bd, 0x8b9, 0x8b4, 0x8b0, 0x8ac, 0x8a8, 0x8a4, 0x8a0, 0x89c, 0x899, 0x895, 0x891, 0x88d, 0x889, 0x885, 0x881, 0x87d, 0x879, 0x875, 0x871, 0x86d, 0x869, 0x865, 0x862, 0x85e, 0x85a, 0x856, 0x852, 0x84e, 0x84a, 0x847, 0x843, 0x83f, 0x83b, 0x837, 0x834, 0x830, 0x82c, 0x828, 0x825, 0x821, 0x81d, 0x819, 0x816, 0x812, 0x80e, 0x80a, 0x807, 0x803, 0x7ff, 0x7fc, 0x7f8, 0x7f4, 0x7f1, 0x7ed, 0x7e9, 0x7e6, 0x7e2, 0x7de, 0x7db, 0x7d7, 0x7d3, 0x7d0, 0x7cc, 0x7c9, 0x7c5, 0x7c1, 0x7be, 0x7ba, 0x7b7, 0x7b3, 0x7b0, 0x7ac, 0x7a8, 0x7a5, 0x7a1, 0x79e, 0x79a, 0x797, 0x793, 0x790, 0x78c, 0x789, 0x785, 0x782, 0x77e, 0x77b, 0x778, 0x774, 0x771, 0x76d, 0x76a, 0x766, 0x763, 0x760, 0x75c, 0x759, 0x755, 0x752, 0x74f, 0x74b, 0x748, 0x744, 0x741, 0x73e, 0x73a, 0x737, 0x734, 0x730, 0x72d, 0x72a, 0x726, 0x723, 0x720, 0x71d, 0x719, 0x716, 0x713, 0x70f, 0x70c, 0x709, 0x706, 0x702, 0x6ff, 0x6fc, 0x6f9, 0x6f6, 0x6f2, 0x6ef, 0x6ec, 0x6e9, 0x6e6, 0x6e2, 0x6df, 0x6dc, 0x6d9, 0x6d6, 0x6d3, 0x6cf, 0x6cc, 0x6c9, 0x6c6, 0x6c3, 0x6c0, 0x6bd, 0x6ba, 0x6b6, 0x6b3, 0x6b0, 0x6ad, 0x6aa, 0x6a7, 0x6a4, 0x6a1, 0x69e, 0x69b, 0x698, 0x695, 0x692, 0x68f, 0x68c, 0x689, 0x685, 0x682, 0x67f, 0x67c, 0x679, 0x676, 0x674, 0x671, 0x66e, 0x66b, 0x668, 0x665, 0x662, 0x65f, 0x65c, 0x659, 0x656, 0x653, 0x650, 0x64d, 0x64a, 0x647, 0x644, 0x642, 0x63f, 0x63c, 0x639, 0x636, 0x633, 0x630, 0x62d, 0x62b, 0x628, 0x625, 0x622, 0x61f, 0x61c, 0x61a, 0x617, 0x614, 0x611, 0x60e, 0x60c, 0x609, 0x606, 0x603, 0x600, 0x5fe, 0x5fb, 0x5f8, 0x5f5, 0x5f3, 0x5f0, 0x5ed, 0x5ea, 0x5e8, 0x5e5, 0x5e2, 0x5e0, 0x5dd, 0x5da, 0x5d7, 0x5d5, 0x5d2, 0x5cf, 0x5cd, 0x5ca, 0x5c7, 0x5c5, 0x5c2, 0x5bf, 0x5bd, 0x5ba, 0x5b7, 0x5b5, 0x5b2, 0x5af, 0x5ad, 0x5aa, 0x5a8, 0x5a5, 0x5a2, 0x5a0, 0x59d, 0x59b, 0x598, 0x595, 0x593, 0x590, 0x58e, 0x58b, 0x589, 0x586, 0x583, 0x581, 0x57e, 0x57c, 0x579, 0x577, 0x574, 0x572, 0x56f, 0x56d, 0x56a, 0x568, 0x565, 0x563, 0x560, 0x55e, 0x55b, 0x559, 0x556, 0x554, 0x551, 0x54f, 0x54d, 0x54a, 0x548, 0x545, 0x543, 0x540, 0x53e, 0x53c, 0x539, 0x537, 0x534, 0x532, 0x52f, 0x52d, 0x52b, 0x528, 0x526, 0x524, 0x521, 0x51f, 0x51c, 0x51a, 0x518, 0x515, 0x513, 0x511, 0x50e, 0x50c, 0x50a, 0x507, 0x505, 0x503, 0x500, 0x4fe, 0x4fc, 0x4f9, 0x4f7, 0x4f5, 0x4f3, 0x4f0, 0x4ee, 0x4ec, 0x4e9, 0x4e7, 0x4e5, 0x4e3, 0x4e0, 0x4de, 0x4dc, 0x4da, 0x4d7, 0x4d5, 0x4d3, 0x4d1, 0x4cf, 0x4cc, 0x4ca, 0x4c8, 0x4c6, 0x4c3, 0x4c1, 0x4bf, 0x4bd, 0x4bb, 0x4b9, 0x4b6, 0x4b4, 0x4b2, 0x4b0, 0x4ae, 0x4ac, 0x4a9, 0x4a7, 0x4a5, 0x4a3, 0x4a1, 0x49f, 0x49d, 0x49a, 0x498, 0x496, 0x494, 0x492, 0x490, 0x48e, 0x48c, 0x489, 0x487, 0x485, 0x483, 0x481, 0x47f, 0x47d, 0x47b, 0x479, 0x477, 0x475, 0x473, 0x471, 0x46f, 0x46c, 0x46a, 0x468, 0x466, 0x464, 0x462, 0x460, 0x45e, 0x45c, 0x45a, 0x458, 0x456, 0x454, 0x452, 0x450, 0x44e, 0x44c, 0x44a, 0x448, 0x446, 0x444, 0x442, 0x440, 0x43e, 0x43c, 0x43b, 0x439, 0x437, 0x435, 0x433, 0x431, 0x42f, 0x42d, 0x42b, 0x429, 0x427, 0x425, 0x423, 0x421, 0x420, 0x41e, 0x41c, 0x41a, 0x418, 0x416, 0x414, 0x412, 0x410, 0x40f, 0x40d, 0x40b, 0x409, 0x407, 0x405, 0x403, 0x401, 0x400, 0x3fe, 0x3fc, 0x3fa, 0x3f8, 0x3f6, 0x3f5, 0x3f3, 0x3f1, 0x3ef, 0x3ed, 0x3eb, 0x3ea, 0x3e8, 0x3e6, 0x3e4, 0x3e2, 0x3e1, 0x3df, 0x3dd, 0x3db, 0x3da, 0x3d8, 0x3d6, 0x3d4, 0x3d2, 0x3d1, 0x3cf, 0x3cd, 0x3cb, 0x3ca, 0x3c8, 0x3c6, 0x3c4, 0x3c3, 0x3c1, 0x3bf, 0x3bd, 0x3bc, 0x3ba, 0x3b8, 0x3b7, 0x3b5, 0x3b3, 0x3b1, 0x3b0, 0x3ae, 0x3ac, 0x3ab, 0x3a9, 0x3a7, 0x3a6, 0x3a4, 0x3a2, 0x3a1, 0x39f, 0x39d, 0x39c, 0x39a, 0x398, 0x397, 0x395, 0x393, 0x392, 0x390, 0x38e, 0x38d, 0x38b, 0x389, 0x388, 0x386, 0x384, 0x383, 0x381, 0x380, 0x37e, 0x37c, 0x37b, 0x379, 0x378, 0x376, 0x374, 0x373, 0x371, 0x370, 0x36e, 0x36c, 0x36b, 0x369, 0x368, 0x366, 0x365, 0x363, 0x361, 0x360, 0x35e, 0x35d, 0x35b, 0x35a, 0x358, 0x357, 0x355, 0x353, 0x352, 0x350, 0x34f, 0x34d, 0x34c, 0x34a, 0x349, 0x347, 0x346, 0x344, 0x343, 0x341, 0x340, 0x33e, 0x33d, 0x33b, 0x33a, 0x338, 0x337, 0x335, 0x334, 0x332, 0x331, 0x32f, 0x32e, 0x32c, 0x32b, 0x32a, 0x328, 0x327, 0x325, 0x324, 0x322, 0x321, 0x31f, 0x31e, 0x31c, 0x31b, 0x31a, 0x318, 0x317, 0x315, 0x314, 0x312, 0x311, 0x310, 0x30e, 0x30d, 0x30b, 0x30a, 0x309, 0x307, 0x306, 0x304, 0x303, 0x302, 0x300, 0x2ff, 0x2fd, 0x2fc, 0x2fb, 0x2f9, 0x2f8, 0x2f7, 0x2f5, 0x2f4, 0x2f2, 0x2f1, 0x2f0, 0x2ee, 0x2ed, 0x2ec, 0x2ea, 0x2e9, 0x2e8, 0x2e6, 0x2e5, 0x2e4, 0x2e2, 0x2e1, 0x2e0, 0x2de, 0x2dd, 0x2dc, 0x2da, 0x2d9, 0x2d8, 0x2d6, 0x2d5, 0x2d4, 0x2d3, 0x2d1, 0x2d0, 0x2cf, 0x2cd, 0x2cc, 0x2cb, 0x2c9, 0x2c8, 0x2c7, 0x2c6, 0x2c4, 0x2c3, 0x2c2, 0x2c0, 0x2bf, 0x2be, 0x2bd, 0x2bb, 0x2ba, 0x2b9, 0x2b8, 0x2b6, 0x2b5, 0x2b4, 0x2b3, 0x2b1, 0x2b0, 0x2af, 0x2ae, 0x2ac, 0x2ab, 0x2aa, 0x2a9, 0x2a7, 0x2a6, 0x2a5, 0x2a4, 0x2a3, 0x2a1, 0x2a0, 0x29f, 0x29e, 0x29d, 0x29b, 0x29a, 0x299, 0x298, 0x297, 0x295, 0x294, 0x293, 0x292, 0x291, 0x28f, 0x28e, 0x28d, 0x28c, 0x28b, 0x28a, 0x288, 0x287, 0x286, 0x285, 0x284, 0x283, 0x281, 0x280, 0x27f, 0x27e, 0x27d, 0x27c, 0x27a, 0x279, 0x278, 0x277, 0x276, 0x275, 0x274, 0x272, 0x271, 0x270, 0x26f, 0x26e, 0x26d, 0x26c, 0x26b, 0x26a, 0x268, 0x267, 0x266, 0x265, 0x264, 0x263, 0x262, 0x261, 0x260, 0x25e, 0x25d, 0x25c, 0x25b, 0x25a, 0x259, 0x258, 0x257, 0x256, 0x255, 0x254, 0x253, 0x251, 0x250, 0x24f, 0x24e, 0x24d, 0x24c, 0x24b, 0x24a, 0x249, 0x248, 0x247, 0x246, 0x245, 0x244, 0x243, 0x242, 0x241, 0x240, 0x23e, 0x23d, 0x23c, 0x23b, 0x23a, 0x239, 0x238, 0x237, 0x236, 0x235, 0x234, 0x233, 0x232, 0x231, 0x230, 0x22f, 0x22e, 0x22d, 0x22c, 0x22b, 0x22a, 0x229, 0x228, 0x227, 0x226, 0x225, 0x224, 0x223, 0x222, 0x221, 0x220, 0x21f, 0x21e, 0x21d, 0x21c, 0x21b, 0x21a, 0x219, 0x218, 0x217, 0x216, 0x216, 0x215, 0x214, 0x213, 0x212, 0x211, 0x210, 0x20f, 0x20e, 0x20d, 0x20c, 0x20b, 0x20a, 0x209, 0x208, 0x207, 0x206, 0x205, 0x204, 0x204, 0x203, 0x202, 0x201, 0x200, 0x1ff, 0x1fe, 0x1fd, 0x1fc, 0x1fb, 0x1fa, 0x1f9, 0x1f8, 0x1f8, 0x1f7, 0x1f6, 0x1f5, 0x1f4, 0x1f3, 0x1f2, 0x1f1, 0x1f0, 0x1ef, 0x1ef, 0x1ee, 0x1ed, 0x1ec, 0x1eb, 0x1ea, 0x1e9, 0x1e8, 0x1e7, 0x1e7, 0x1e6, 0x1e5, 0x1e4, 0x1e3, 0x1e2, 0x1e1, 0x1e0, 0x1e0, 0x1df, 0x1de, 0x1dd, 0x1dc, 0x1db, 0x1da, 0x1da, 0x1d9, 0x1d8, 0x1d7, 0x1d6, 0x1d5, 0x1d4, 0x1d4, 0x1d3, 0x1d2, 0x1d1, 0x1d0, 0x1cf, 0x1cf, 0x1ce, 0x1cd, 0x1cc, 0x1cb, 0x1ca, 0x1ca, 0x1c9, 0x1c8, 0x1c7, 0x1c6, 0x1c6, 0x1c5, 0x1c4, 0x1c3, 0x1c2, 0x1c1, 0x1c1, 0x1c0, 0x1bf, 0x1be, 0x1bd, 0x1bd, 0x1bc, 0x1bb, 0x1ba, 0x1b9, 0x1b9, 0x1b8, 0x1b7, 0x1b6, 0x1b5, 0x1b5, 0x1b4, 0x1b3, 0x1b2, 0x1b1, 0x1b1, 0x1b0, 0x1af, 0x1ae, 0x1ae, 0x1ad, 0x1ac, 0x1ab, 0x1ab, 0x1aa, 0x1a9, 0x1a8, 0x1a7, 0x1a7, 0x1a6, 0x1a5, 0x1a4, 0x1a4, 0x1a3, 0x1a2, 0x1a1, 0x1a1, 0x1a0, 0x19f, 0x19e, 0x19e, 0x19d, 0x19c, 0x19b, 0x19b, 0x19a, 0x199, 0x198, 0x198, 0x197, 0x196, 0x195, 0x195, 0x194, 0x193, 0x193, 0x192, 0x191, 0x190, 0x190, 0x18f, 0x18e, 0x18e, 0x18d, 0x18c, 0x18b, 0x18b, 0x18a, 0x189, 0x189, 0x188, 0x187, 0x186, 0x186, 0x185, 0x184, 0x184, 0x183, 0x182, 0x182, 0x181, 0x180, 0x17f, 0x17f, 0x17e, 0x17d, 0x17d, 0x17c, 0x17b, 0x17b, 0x17a, 0x179, 0x179, 0x178, 0x177, 0x177, 0x176, 0x175, 0x175, 0x174, 0x173, 0x172, 0x172, 0x171, 0x170, 0x170, 0x16f, 0x16f, 0x16e, 0x16d, 0x16d, 0x16c, 0x16b, 0x16b, 0x16a, 0x169, 0x169, 0x168, 0x167, 0x167, 0x166, 0x165, 0x165, 0x164, 0x163, 0x163, 0x162, 0x162, 0x161, 0x160, 0x160, 0x15f, 0x15e, 0x15e, 0x15d, 0x15c, 0x15c, 0x15b, 0x15b, 0x15a, 0x159, 0x159, 0x158, 0x157, 0x157, 0x156, 0x156, 0x155, 0x154, 0x154, 0x153, 0x153, 0x152, 0x151, 0x151, 0x150, 0x14f, 0x14f, 0x14e, 0x14e, 0x14d, 0x14c, 0x14c, 0x14b, 0x14b, 0x14a, 0x149, 0x149, 0x148, 0x148, 0x147, 0x147, 0x146, 0x145, 0x145, 0x144, 0x144, 0x143, 0x142, 0x142, 0x141, 0x141, 0x140, 0x140, 0x13f, 0x13e, 0x13e, 0x13d, 0x13d, 0x13c, 0x13c, 0x13b, 0x13a, 0x13a, 0x139, 0x139, 0x138, 0x138, 0x137, 0x136, 0x136, 0x135, 0x135, 0x134, 0x134, 0x133, 0x133, 0x132, 0x131, 0x131, 0x130, 0x130, 0x12f, 0x12f, 0x12e, 0x12e, 0x12d, 0x12d, 0x12c, 0x12b, 0x12b, 0x12a, 0x12a, 0x129, 0x129, 0x128, 0x128, 0x127, 0x127, 0x126, 0x126, 0x125, 0x124, 0x124, 0x123, 0x123, 0x122, 0x122, 0x121, 0x121, 0x120, 0x120, 0x11f, 0x11f, 0x11e, 0x11e, 0x11d, 0x11d, 0x11c, 0x11c, 0x11b, 0x11b, 0x11a, 0x11a, 0x119, 0x119, 0x118, 0x118, 0x117, 0x117, 0x116, 0x116, 0x115, 0x115, 0x114, 0x114, 0x113, 0x113, 0x112, 0x112, 0x111, 0x111, 0x110, 0x110, 0x10f, 0x10f, 0x10e, 0x10e, 0x10d, 0x10d, 0x10c, 0x10c, 0x10b, 0x10b, 0x10a, 0x10a, 0x109, 0x109, 0x108, 0x108, 0x107, 0x107, 0x106, 0x106, 0x106, 0x105, 0x105, 0x104, 0x104, 0x103, 0x103, 0x102, 0x102, 0x101, 0x101, 0x100, 0x100, 0x0ff, 0x0ff, 0x0ff, 0x0fe, 0x0fe, 0x0fd, 0x0fd, 0x0fc, 0x0fc, 0x0fb, 0x0fb, 0x0fa, 0x0fa, 0x0fa, 0x0f9, 0x0f9, 0x0f8, 0x0f8, 0x0f7, 0x0f7, 0x0f6, 0x0f6, 0x0f5, 0x0f5, 0x0f5, 0x0f4, 0x0f4, 0x0f3, 0x0f3, 0x0f2, 0x0f2, 0x0f2, 0x0f1, 0x0f1, 0x0f0, 0x0f0, 0x0ef, 0x0ef, 0x0ef, 0x0ee, 0x0ee, 0x0ed, 0x0ed, 0x0ec, 0x0ec, 0x0ec, 0x0eb, 0x0eb, 0x0ea, 0x0ea, 0x0e9, 0x0e9, 0x0e9, 0x0e8, 0x0e8, 0x0e7, 0x0e7, 0x0e6, 0x0e6, 0x0e6, 0x0e5, 0x0e5, 0x0e4, 0x0e4, 0x0e4, 0x0e3, 0x0e3, 0x0e2, 0x0e2, 0x0e2, 0x0e1, 0x0e1, 0x0e0, 0x0e0, 0x0e0, 0x0df, 0x0df, 0x0de, 0x0de, 0x0dd, 0x0dd, 0x0dd, 0x0dc, 0x0dc, 0x0dc, 0x0db, 0x0db, 0x0da, 0x0da, 0x0da, 0x0d9, 0x0d9, 0x0d8, 0x0d8, 0x0d8, 0x0d7, 0x0d7, 0x0d6, 0x0d6, 0x0d6, 0x0d5, 0x0d5, 0x0d4, 0x0d4, 0x0d4, 0x0d3, 0x0d3, 0x0d3, 0x0d2, 0x0d2, 0x0d1, 0x0d1, 0x0d1, 0x0d0, 0x0d0, 0x0d0, 0x0cf, 0x0cf, 0x0ce, 0x0ce, 0x0ce, 0x0cd, 0x0cd, 0x0cd, 0x0cc, 0x0cc, 0x0cb, 0x0cb, 0x0cb, 0x0ca, 0x0ca, 0x0ca, 0x0c9, 0x0c9, 0x0c9, 0x0c8, 0x0c8, 0x0c7, 0x0c7, 0x0c7, 0x0c6, 0x0c6, 0x0c6, 0x0c5, 0x0c5, 0x0c5, 0x0c4, 0x0c4, 0x0c4, 0x0c3, 0x0c3, 0x0c3, 0x0c2, 0x0c2, 0x0c1, 0x0c1, 0x0c1, 0x0c0, 0x0c0, 0x0c0, 0x0bf, 0x0bf, 0x0bf, 0x0be, 0x0be, 0x0be, 0x0bd, 0x0bd, 0x0bd, 0x0bc, 0x0bc, 0x0bc, 0x0bb, 0x0bb, 0x0bb, 0x0ba, 0x0ba, 0x0ba, 0x0b9, 0x0b9, 0x0b9, 0x0b8, 0x0b8, 0x0b8, 0x0b7, 0x0b7, 0x0b7, 0x0b6, 0x0b6, 0x0b6, 0x0b5, 0x0b5, 0x0b5, 0x0b4, 0x0b4, 0x0b4, 0x0b3, 0x0b3, 0x0b3, 0x0b2, 0x0b2, 0x0b2, 0x0b1, 0x0b1, 0x0b1, 0x0b0, 0x0b0, 0x0b0, 0x0af, 0x0af, 0x0af, 0x0af, 0x0ae, 0x0ae, 0x0ae, 0x0ad, 0x0ad, 0x0ad, 0x0ac, 0x0ac, 0x0ac, 0x0ab, 0x0ab, 0x0ab, 0x0aa, 0x0aa, 0x0aa, 0x0aa, 0x0a9, 0x0a9, 0x0a9, 0x0a8, 0x0a8, 0x0a8, 0x0a7, 0x0a7, 0x0a7, 0x0a7, 0x0a6, 0x0a6, 0x0a6, 0x0a5, 0x0a5, 0x0a5, 0x0a4, 0x0a4, 0x0a4, 0x0a4, 0x0a3, 0x0a3, 0x0a3, 0x0a2, 0x0a2, 0x0a2, 0x0a2, 0x0a1, 0x0a1, 0x0a1, 0x0a0, 0x0a0, 0x0a0, 0x09f, 0x09f, 0x09f, 0x09f, 0x09e, 0x09e, 0x09e, 0x09d, 0x09d, 0x09d, 0x09d, 0x09c, 0x09c, 0x09c, 0x09b, 0x09b, 0x09b, 0x09b, 0x09a, 0x09a, 0x09a, 0x09a, 0x099, 0x099, 0x099, 0x098, 0x098, 0x098, 0x098, 0x097, 0x097, 0x097, 0x097, 0x096, 0x096, 0x096, 0x095, 0x095, 0x095, 0x095, 0x094, 0x094, 0x094, 0x094, 0x093, 0x093, 0x093, 0x093, 0x092, 0x092, 0x092, 0x091, 0x091, 0x091, 0x091, 0x090, 0x090, 0x090, 0x090, 0x08f, 0x08f, 0x08f, 0x08f, 0x08e, 0x08e, 0x08e, 0x08e, 0x08d, 0x08d, 0x08d, 0x08d, 0x08c, 0x08c, 0x08c, 0x08c, 0x08b, 0x08b, 0x08b, 0x08b, 0x08a, 0x08a, 0x08a, 0x08a, 0x089, 0x089, 0x089, 0x089, 0x088, 0x088, 0x088, 0x088, 0x087, 0x087, 0x087, 0x087, 0x086, 0x086, 0x086, 0x086, 0x085, 0x085, 0x085, 0x085, 0x084, 0x084, 0x084, 0x084, 0x083, 0x083, 0x083, 0x083, 0x083, 0x082, 0x082, 0x082, 0x082, 0x081, 0x081, 0x081, 0x081, 0x080, 0x080, 0x080, 0x080, 0x07f, 0x07f, 0x07f, 0x07f, 0x07f, 0x07e, 0x07e, 0x07e, 0x07e, 0x07d, 0x07d, 0x07d, 0x07d, 0x07d, 0x07c, 0x07c, 0x07c, 0x07c, 0x07b, 0x07b, 0x07b, 0x07b, 0x07b, 0x07a, 0x07a, 0x07a, 0x07a, 0x079, 0x079, 0x079, 0x079, 0x079, 0x078, 0x078, 0x078, 0x078, 0x077, 0x077, 0x077, 0x077, 0x077, 0x076, 0x076, 0x076, 0x076, 0x076, 0x075, 0x075, 0x075, 0x075, 0x074, 0x074, 0x074, 0x074, 0x074, 0x073, 0x073, 0x073, 0x073, 0x073, 0x072, 0x072, 0x072, 0x072, 0x072, 0x071, 0x071, 0x071, 0x071, 0x071, 0x070, 0x070, 0x070, 0x070, 0x070, 0x06f, 0x06f, 0x06f, 0x06f, 0x06f, 0x06e, 0x06e, 0x06e, 0x06e, 0x06e, 0x06d, 0x06d, 0x06d, 0x06d, 0x06d, 0x06c, 0x06c, 0x06c, 0x06c, 0x06c, 0x06b, 0x06b, 0x06b, 0x06b, 0x06b, 0x06a, 0x06a, 0x06a, 0x06a, 0x06a, 0x069, 0x069, 0x069, 0x069, 0x069, 0x069, 0x068, 0x068, 0x068, 0x068, 0x068, 0x067, 0x067, 0x067, 0x067, 0x067, 0x066, 0x066, 0x066, 0x066, 0x066, 0x066, 0x065, 0x065, 0x065, 0x065, 0x065, 0x064, 0x064, 0x064, 0x064, 0x064, 0x064, 0x063, 0x063, 0x063, 0x063, 0x063, 0x062, 0x062, 0x062, 0x062, 0x062, 0x062, 0x061, 0x061, 0x061, 0x061, 0x061, 0x061, 0x060, 0x060, 0x060, 0x060, 0x060, 0x060, 0x05f, 0x05f, 0x05f, 0x05f, 0x05f, 0x05e, 0x05e, 0x05e, 0x05e, 0x05e, 0x05e, 0x05d, 0x05d, 0x05d, 0x05d, 0x05d, 0x05d, 0x05c, 0x05c, 0x05c, 0x05c, 0x05c, 0x05c, 0x05b, 0x05b, 0x05b, 0x05b, 0x05b, 0x05b, 0x05a, 0x05a, 0x05a, 0x05a, 0x05a, 0x05a, 0x05a, 0x059, 0x059, 0x059, 0x059, 0x059, 0x059, 0x058, 0x058, 0x058, 0x058, 0x058, 0x058, 0x057, 0x057, 0x057, 0x057, 0x057, 0x057, 0x056, 0x056, 0x056, 0x056, 0x056, 0x056, 0x056, 0x055, 0x055, 0x055, 0x055, 0x055, 0x055, 0x054, 0x054, 0x054, 0x054, 0x054, 0x054, 0x054, 0x053, 0x053, 0x053, 0x053, 0x053, 0x053, 0x053, 0x052, 0x052, 0x052, 0x052, 0x052, 0x052, 0x051, 0x051, 0x051, 0x051, 0x051, 0x051, 0x051, 0x050, 0x050, 0x050, 0x050, 0x050, 0x050, 0x050, 0x04f, 0x04f, 0x04f, 0x04f, 0x04f, 0x04f, 0x04f, 0x04e, 0x04e, 0x04e, 0x04e, 0x04e, 0x04e, 0x04e, 0x04d, 0x04d, 0x04d, 0x04d, 0x04d, 0x04d, 0x04d, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04b, 0x04b, 0x04b, 0x04b, 0x04b, 0x04b, 0x04b, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x049, 0x049, 0x049, 0x049, 0x049, 0x049, 0x049, 0x048, 0x048, 0x048, 0x048, 0x048, 0x048, 0x048, 0x048, 0x047, 0x047, 0x047, 0x047, 0x047, 0x047, 0x047, 0x047, 0x046, 0x046, 0x046, 0x046, 0x046, 0x046, 0x046, 0x046, 0x045, 0x045, 0x045, 0x045, 0x045, 0x045, 0x045, 0x045, 0x044, 0x044, 0x044, 0x044, 0x044, 0x044, 0x044, 0x044, 0x043, 0x043, 0x043, 0x043, 0x043, 0x043, 0x043, 0x043, 0x042, 0x042, 0x042, 0x042, 0x042, 0x042, 0x042, 0x042, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f }; static_assert(sizeof(centTableSSGSquare) == sizeof(uint16_t) * Note::ABS_PITCH_RANGE, "Invalid SSG square cent table size."); const uint16_t centTableSSGTriangle[3072] = { 0x077, 0x077, 0x077, 0x077, 0x076, 0x076, 0x076, 0x076, 0x076, 0x075, 0x075, 0x075, 0x075, 0x074, 0x074, 0x074, 0x074, 0x074, 0x073, 0x073, 0x073, 0x073, 0x073, 0x072, 0x072, 0x072, 0x072, 0x072, 0x071, 0x071, 0x071, 0x071, 0x071, 0x070, 0x070, 0x070, 0x070, 0x070, 0x06f, 0x06f, 0x06f, 0x06f, 0x06f, 0x06e, 0x06e, 0x06e, 0x06e, 0x06e, 0x06d, 0x06d, 0x06d, 0x06d, 0x06d, 0x06c, 0x06c, 0x06c, 0x06c, 0x06c, 0x06b, 0x06b, 0x06b, 0x06b, 0x06b, 0x06a, 0x06a, 0x06a, 0x06a, 0x06a, 0x069, 0x069, 0x069, 0x069, 0x069, 0x069, 0x068, 0x068, 0x068, 0x068, 0x068, 0x067, 0x067, 0x067, 0x067, 0x067, 0x066, 0x066, 0x066, 0x066, 0x066, 0x066, 0x065, 0x065, 0x065, 0x065, 0x065, 0x064, 0x064, 0x064, 0x064, 0x064, 0x064, 0x063, 0x063, 0x063, 0x063, 0x063, 0x062, 0x062, 0x062, 0x062, 0x062, 0x062, 0x061, 0x061, 0x061, 0x061, 0x061, 0x061, 0x060, 0x060, 0x060, 0x060, 0x060, 0x060, 0x05f, 0x05f, 0x05f, 0x05f, 0x05f, 0x05e, 0x05e, 0x05e, 0x05e, 0x05e, 0x05e, 0x05d, 0x05d, 0x05d, 0x05d, 0x05d, 0x05d, 0x05c, 0x05c, 0x05c, 0x05c, 0x05c, 0x05c, 0x05b, 0x05b, 0x05b, 0x05b, 0x05b, 0x05b, 0x05a, 0x05a, 0x05a, 0x05a, 0x05a, 0x05a, 0x05a, 0x059, 0x059, 0x059, 0x059, 0x059, 0x059, 0x058, 0x058, 0x058, 0x058, 0x058, 0x058, 0x057, 0x057, 0x057, 0x057, 0x057, 0x057, 0x056, 0x056, 0x056, 0x056, 0x056, 0x056, 0x056, 0x055, 0x055, 0x055, 0x055, 0x055, 0x055, 0x054, 0x054, 0x054, 0x054, 0x054, 0x054, 0x054, 0x053, 0x053, 0x053, 0x053, 0x053, 0x053, 0x053, 0x052, 0x052, 0x052, 0x052, 0x052, 0x052, 0x051, 0x051, 0x051, 0x051, 0x051, 0x051, 0x051, 0x050, 0x050, 0x050, 0x050, 0x050, 0x050, 0x050, 0x04f, 0x04f, 0x04f, 0x04f, 0x04f, 0x04f, 0x04f, 0x04e, 0x04e, 0x04e, 0x04e, 0x04e, 0x04e, 0x04e, 0x04d, 0x04d, 0x04d, 0x04d, 0x04d, 0x04d, 0x04d, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04b, 0x04b, 0x04b, 0x04b, 0x04b, 0x04b, 0x04b, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x049, 0x049, 0x049, 0x049, 0x049, 0x049, 0x049, 0x048, 0x048, 0x048, 0x048, 0x048, 0x048, 0x048, 0x048, 0x047, 0x047, 0x047, 0x047, 0x047, 0x047, 0x047, 0x047, 0x046, 0x046, 0x046, 0x046, 0x046, 0x046, 0x046, 0x046, 0x045, 0x045, 0x045, 0x045, 0x045, 0x045, 0x045, 0x045, 0x044, 0x044, 0x044, 0x044, 0x044, 0x044, 0x044, 0x044, 0x043, 0x043, 0x043, 0x043, 0x043, 0x043, 0x043, 0x043, 0x042, 0x042, 0x042, 0x042, 0x042, 0x042, 0x042, 0x042, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001 }; static_assert(sizeof(centTableSSGTriangle) == sizeof(uint16_t) * Note::ABS_PITCH_RANGE, "Invalid SSG triangle cent table size."); const uint16_t centTableSSGSaw[3072] = { 0x0ef, 0x0ee, 0x0ee, 0x0ed, 0x0ed, 0x0ec, 0x0ec, 0x0ec, 0x0eb, 0x0eb, 0x0ea, 0x0ea, 0x0e9, 0x0e9, 0x0e9, 0x0e8, 0x0e8, 0x0e7, 0x0e7, 0x0e6, 0x0e6, 0x0e6, 0x0e5, 0x0e5, 0x0e4, 0x0e4, 0x0e4, 0x0e3, 0x0e3, 0x0e2, 0x0e2, 0x0e2, 0x0e1, 0x0e1, 0x0e0, 0x0e0, 0x0e0, 0x0df, 0x0df, 0x0de, 0x0de, 0x0dd, 0x0dd, 0x0dd, 0x0dc, 0x0dc, 0x0dc, 0x0db, 0x0db, 0x0da, 0x0da, 0x0da, 0x0d9, 0x0d9, 0x0d8, 0x0d8, 0x0d8, 0x0d7, 0x0d7, 0x0d6, 0x0d6, 0x0d6, 0x0d5, 0x0d5, 0x0d4, 0x0d4, 0x0d4, 0x0d3, 0x0d3, 0x0d3, 0x0d2, 0x0d2, 0x0d1, 0x0d1, 0x0d1, 0x0d0, 0x0d0, 0x0d0, 0x0cf, 0x0cf, 0x0ce, 0x0ce, 0x0ce, 0x0cd, 0x0cd, 0x0cd, 0x0cc, 0x0cc, 0x0cb, 0x0cb, 0x0cb, 0x0ca, 0x0ca, 0x0ca, 0x0c9, 0x0c9, 0x0c9, 0x0c8, 0x0c8, 0x0c7, 0x0c7, 0x0c7, 0x0c6, 0x0c6, 0x0c6, 0x0c5, 0x0c5, 0x0c5, 0x0c4, 0x0c4, 0x0c4, 0x0c3, 0x0c3, 0x0c3, 0x0c2, 0x0c2, 0x0c1, 0x0c1, 0x0c1, 0x0c0, 0x0c0, 0x0c0, 0x0bf, 0x0bf, 0x0bf, 0x0be, 0x0be, 0x0be, 0x0bd, 0x0bd, 0x0bd, 0x0bc, 0x0bc, 0x0bc, 0x0bb, 0x0bb, 0x0bb, 0x0ba, 0x0ba, 0x0ba, 0x0b9, 0x0b9, 0x0b9, 0x0b8, 0x0b8, 0x0b8, 0x0b7, 0x0b7, 0x0b7, 0x0b6, 0x0b6, 0x0b6, 0x0b5, 0x0b5, 0x0b5, 0x0b4, 0x0b4, 0x0b4, 0x0b3, 0x0b3, 0x0b3, 0x0b2, 0x0b2, 0x0b2, 0x0b1, 0x0b1, 0x0b1, 0x0b0, 0x0b0, 0x0b0, 0x0af, 0x0af, 0x0af, 0x0af, 0x0ae, 0x0ae, 0x0ae, 0x0ad, 0x0ad, 0x0ad, 0x0ac, 0x0ac, 0x0ac, 0x0ab, 0x0ab, 0x0ab, 0x0aa, 0x0aa, 0x0aa, 0x0aa, 0x0a9, 0x0a9, 0x0a9, 0x0a8, 0x0a8, 0x0a8, 0x0a7, 0x0a7, 0x0a7, 0x0a7, 0x0a6, 0x0a6, 0x0a6, 0x0a5, 0x0a5, 0x0a5, 0x0a4, 0x0a4, 0x0a4, 0x0a4, 0x0a3, 0x0a3, 0x0a3, 0x0a2, 0x0a2, 0x0a2, 0x0a2, 0x0a1, 0x0a1, 0x0a1, 0x0a0, 0x0a0, 0x0a0, 0x09f, 0x09f, 0x09f, 0x09f, 0x09e, 0x09e, 0x09e, 0x09d, 0x09d, 0x09d, 0x09d, 0x09c, 0x09c, 0x09c, 0x09b, 0x09b, 0x09b, 0x09b, 0x09a, 0x09a, 0x09a, 0x09a, 0x099, 0x099, 0x099, 0x098, 0x098, 0x098, 0x098, 0x097, 0x097, 0x097, 0x097, 0x096, 0x096, 0x096, 0x095, 0x095, 0x095, 0x095, 0x094, 0x094, 0x094, 0x094, 0x093, 0x093, 0x093, 0x093, 0x092, 0x092, 0x092, 0x091, 0x091, 0x091, 0x091, 0x090, 0x090, 0x090, 0x090, 0x08f, 0x08f, 0x08f, 0x08f, 0x08e, 0x08e, 0x08e, 0x08e, 0x08d, 0x08d, 0x08d, 0x08d, 0x08c, 0x08c, 0x08c, 0x08c, 0x08b, 0x08b, 0x08b, 0x08b, 0x08a, 0x08a, 0x08a, 0x08a, 0x089, 0x089, 0x089, 0x089, 0x088, 0x088, 0x088, 0x088, 0x087, 0x087, 0x087, 0x087, 0x086, 0x086, 0x086, 0x086, 0x085, 0x085, 0x085, 0x085, 0x084, 0x084, 0x084, 0x084, 0x083, 0x083, 0x083, 0x083, 0x083, 0x082, 0x082, 0x082, 0x082, 0x081, 0x081, 0x081, 0x081, 0x080, 0x080, 0x080, 0x080, 0x07f, 0x07f, 0x07f, 0x07f, 0x07f, 0x07e, 0x07e, 0x07e, 0x07e, 0x07d, 0x07d, 0x07d, 0x07d, 0x07d, 0x07c, 0x07c, 0x07c, 0x07c, 0x07b, 0x07b, 0x07b, 0x07b, 0x07b, 0x07a, 0x07a, 0x07a, 0x07a, 0x079, 0x079, 0x079, 0x079, 0x079, 0x078, 0x078, 0x078, 0x078, 0x077, 0x077, 0x077, 0x077, 0x077, 0x076, 0x076, 0x076, 0x076, 0x076, 0x075, 0x075, 0x075, 0x075, 0x074, 0x074, 0x074, 0x074, 0x074, 0x073, 0x073, 0x073, 0x073, 0x073, 0x072, 0x072, 0x072, 0x072, 0x072, 0x071, 0x071, 0x071, 0x071, 0x071, 0x070, 0x070, 0x070, 0x070, 0x070, 0x06f, 0x06f, 0x06f, 0x06f, 0x06f, 0x06e, 0x06e, 0x06e, 0x06e, 0x06e, 0x06d, 0x06d, 0x06d, 0x06d, 0x06d, 0x06c, 0x06c, 0x06c, 0x06c, 0x06c, 0x06b, 0x06b, 0x06b, 0x06b, 0x06b, 0x06a, 0x06a, 0x06a, 0x06a, 0x06a, 0x069, 0x069, 0x069, 0x069, 0x069, 0x069, 0x068, 0x068, 0x068, 0x068, 0x068, 0x067, 0x067, 0x067, 0x067, 0x067, 0x066, 0x066, 0x066, 0x066, 0x066, 0x066, 0x065, 0x065, 0x065, 0x065, 0x065, 0x064, 0x064, 0x064, 0x064, 0x064, 0x064, 0x063, 0x063, 0x063, 0x063, 0x063, 0x062, 0x062, 0x062, 0x062, 0x062, 0x062, 0x061, 0x061, 0x061, 0x061, 0x061, 0x061, 0x060, 0x060, 0x060, 0x060, 0x060, 0x060, 0x05f, 0x05f, 0x05f, 0x05f, 0x05f, 0x05e, 0x05e, 0x05e, 0x05e, 0x05e, 0x05e, 0x05d, 0x05d, 0x05d, 0x05d, 0x05d, 0x05d, 0x05c, 0x05c, 0x05c, 0x05c, 0x05c, 0x05c, 0x05b, 0x05b, 0x05b, 0x05b, 0x05b, 0x05b, 0x05a, 0x05a, 0x05a, 0x05a, 0x05a, 0x05a, 0x05a, 0x059, 0x059, 0x059, 0x059, 0x059, 0x059, 0x058, 0x058, 0x058, 0x058, 0x058, 0x058, 0x057, 0x057, 0x057, 0x057, 0x057, 0x057, 0x056, 0x056, 0x056, 0x056, 0x056, 0x056, 0x056, 0x055, 0x055, 0x055, 0x055, 0x055, 0x055, 0x054, 0x054, 0x054, 0x054, 0x054, 0x054, 0x054, 0x053, 0x053, 0x053, 0x053, 0x053, 0x053, 0x053, 0x052, 0x052, 0x052, 0x052, 0x052, 0x052, 0x051, 0x051, 0x051, 0x051, 0x051, 0x051, 0x051, 0x050, 0x050, 0x050, 0x050, 0x050, 0x050, 0x050, 0x04f, 0x04f, 0x04f, 0x04f, 0x04f, 0x04f, 0x04f, 0x04e, 0x04e, 0x04e, 0x04e, 0x04e, 0x04e, 0x04e, 0x04d, 0x04d, 0x04d, 0x04d, 0x04d, 0x04d, 0x04d, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04b, 0x04b, 0x04b, 0x04b, 0x04b, 0x04b, 0x04b, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x049, 0x049, 0x049, 0x049, 0x049, 0x049, 0x049, 0x048, 0x048, 0x048, 0x048, 0x048, 0x048, 0x048, 0x048, 0x047, 0x047, 0x047, 0x047, 0x047, 0x047, 0x047, 0x047, 0x046, 0x046, 0x046, 0x046, 0x046, 0x046, 0x046, 0x046, 0x045, 0x045, 0x045, 0x045, 0x045, 0x045, 0x045, 0x045, 0x044, 0x044, 0x044, 0x044, 0x044, 0x044, 0x044, 0x044, 0x043, 0x043, 0x043, 0x043, 0x043, 0x043, 0x043, 0x043, 0x042, 0x042, 0x042, 0x042, 0x042, 0x042, 0x042, 0x042, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001 }; static_assert(sizeof(centTableSSGSaw) == sizeof(uint16_t) * Note::ABS_PITCH_RANGE, "Invalid SSG saw cent table size."); } uint16_t calculateFNumber(int absPitch, int finePitch) { uint16_t p = centTableFM[absPitch]; return (finePitch ? (finePitch > 0 ? p + finePitch : p - static_cast(-finePitch)) : p); } uint16_t calculateSSGSquareTP(int absPitch, int finePitch) { uint16_t p = centTableSSGSquare[absPitch]; return (finePitch ? (finePitch > 0 ? p - finePitch : p + static_cast(-finePitch)) : p); } uint16_t calculateSSGTriangleEP(int absPitch, int finePitch) { uint16_t p = centTableSSGTriangle[absPitch]; return (finePitch ? (finePitch > 0 ? p - finePitch : p + static_cast(-finePitch)) : p); } uint16_t calculateSSGSawEP(int absPitch, int finePitch) { uint16_t p = centTableSSGSaw[absPitch]; return (finePitch ? (finePitch > 0 ? p - finePitch : p + static_cast(-finePitch)) : p); } } Note::Note(int noteNum) : request_eval_(false) { if (noteNum < 0) { octave_ = 0; name_ = NoteName::C; pitch_ = 0; return; } octave_ = noteNum / 12; if (octave_ > OCTAVE_RANGE - 1) { octave_ = OCTAVE_RANGE - 1; name_ = NoteName::B; pitch_ = SEMITONE_PITCH - 1; return; } name_ = static_cast(noteNum % 12); pitch_ = 0; } void Note::evaluateState(int octave, int note, int pitch) { pitch_ = pitch % SEMITONE_PITCH; note += pitch / SEMITONE_PITCH; name_ = ((note % 12) + 12) % 12;; octave_ = (int)(octave + std::floor(note / 12.)); if (octave_ < 0 || (!octave_ && !name_ && pitch_ < 0)) { octave_ = 0; name_ = NoteName::C; pitch_ = 0; } else if (OCTAVE_RANGE <= octave_) { octave_ = OCTAVE_RANGE - 1; name_ = NoteName::B; pitch_ = SEMITONE_PITCH - 1; } request_eval_ = false; } BambooTracker-0.6.5/BambooTracker/note.hpp000066400000000000000000000116161476276175200204540ustar00rootroot00000000000000/* * Copyright (C) 2018-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include class Note; namespace note_utils { uint16_t calculateFNumber(int absPitch, int finePitch); uint16_t calculateSSGSquareTP(int absPitch, int finePitch); uint16_t calculateSSGTriangleEP(int absPitch, int finePitch); uint16_t calculateSSGSawEP(int absPitch, int finePitch); } class Note { public: enum NoteName { C = 0, CS, D, DS, E, F, FS, G, GS, A, AS, B }; static constexpr int OCTAVE_RANGE = 8; static constexpr int DEFAULT_OCTAVE = 4; static constexpr int NOTE_NUMBER_RANGE = OCTAVE_RANGE * 12; // 96 static constexpr int DEFAULT_NOTE_NUM = DEFAULT_OCTAVE * 12; // C4 static constexpr int SEMITONE_PITCH = 32; static constexpr int ABS_PITCH_RANGE = NOTE_NUMBER_RANGE * SEMITONE_PITCH; // 3072 Note(int octave, NoteName name, int pitch = 0); explicit Note(int noteNum = DEFAULT_NOTE_NUM); void setOctave(int octave) noexcept; int getOctave(); void setNoteName(NoteName name) noexcept; NoteName getNotename(); void setPitch(int pitch) noexcept; int getPitch(); int getAbsolutePicth(); int getNoteNumber(); void add(const Note& note); void add(int octave, NoteName name, int pitch = 0); void addPitch(int pitch); void addNoteNumber(int nn); friend bool operator==(Note& a, Note& b); friend bool operator!=(Note& a, Note& b) { return !(a == b); } friend Note operator+(const Note& a, const Note& b); friend Note operator+(const Note& a, int b); friend Note operator+(int a, const Note& b); Note& operator+=(const Note& b); Note& operator+=(int b); private: int octave_; int name_; int pitch_; bool request_eval_; Note(int octave, int name, int pitch); void evaluateState(); void evaluateState(int octave, int note, int pitch); }; inline Note::Note(int octave, NoteName name, int pitch) : octave_(octave), name_(name), pitch_(pitch), request_eval_(true) { } inline Note::Note(int octave, int name, int pitch) : octave_(octave), name_(name), pitch_(pitch), request_eval_(true) { } inline void Note::setOctave(int octave) noexcept { octave_ = octave; request_eval_ = true; } inline int Note::getOctave() { if (request_eval_) evaluateState(); return octave_; } inline void Note::setNoteName(NoteName name) noexcept { name_ = name; request_eval_ = true; } inline Note::NoteName Note::getNotename() { if (request_eval_) evaluateState(); return static_cast(name_); } inline void Note::setPitch(int pitch) noexcept { pitch_ = pitch; request_eval_ = true; } inline int Note::getPitch() { if (request_eval_) evaluateState(); return pitch_; } inline int Note::getAbsolutePicth() { if (request_eval_) evaluateState(); return pitch_ + SEMITONE_PITCH * (name_ + 12 * octave_); } inline int Note::getNoteNumber() { if (request_eval_) evaluateState(); return 12 * octave_ + name_; } inline void Note::add(const Note& note) { add(note.octave_, static_cast(note.name_), note.pitch_); } inline void Note::add(int octave, NoteName name, int pitch) { octave_ += octave; name_ += name; pitch_ += pitch; request_eval_ = true; } inline void Note::addPitch(int pitch) { pitch_ += pitch; request_eval_ = true; } inline void Note::addNoteNumber(int nn) { name_ += nn; request_eval_ = true; } inline void Note::evaluateState() { evaluateState(octave_, name_, pitch_); } inline bool operator==(Note& a, Note& b) { if (a.request_eval_) a.evaluateState(); if (b.request_eval_) b.evaluateState(); return (a.octave_ == b.octave_ && a.name_ == b.name_ && a.pitch_ == b.pitch_); } inline Note operator+(const Note& a, const Note& b) { return Note(a) += b; } inline Note operator+(const Note& a, int b) { return Note(a) += b; } inline Note operator+(int a, const Note& b) { return Note(b) += a; } inline Note& Note::operator+=(const Note& b) { add(b); return *this; } inline Note& Note::operator+=(int b) { addPitch(b); return *this; } BambooTracker-0.6.5/BambooTracker/opna_controller.cpp000066400000000000000000003470021476276175200227030ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2018 Rerrah * SPDX-License-Identifier: MIT */ #include "opna_controller.hpp" #include #include #include "note.hpp" #include "utils.hpp" namespace { constexpr int UNUSED_VALUE = -1; const std::unordered_map> FM_ENV_PARAMS_OP = { { FMOperatorType::All, { FMEnvelopeParameter::AL, FMEnvelopeParameter::FB, FMEnvelopeParameter::AR1, FMEnvelopeParameter::DR1, FMEnvelopeParameter::SR1, FMEnvelopeParameter::RR1, FMEnvelopeParameter::SL1, FMEnvelopeParameter::TL1, FMEnvelopeParameter::KS1, FMEnvelopeParameter::ML1, FMEnvelopeParameter::DT1, FMEnvelopeParameter::AR2, FMEnvelopeParameter::DR2, FMEnvelopeParameter::SR2, FMEnvelopeParameter::RR2, FMEnvelopeParameter::SL2, FMEnvelopeParameter::TL2, FMEnvelopeParameter::KS2, FMEnvelopeParameter::ML2, FMEnvelopeParameter::DT2, FMEnvelopeParameter::AR3, FMEnvelopeParameter::DR3, FMEnvelopeParameter::SR3, FMEnvelopeParameter::RR3, FMEnvelopeParameter::SL3, FMEnvelopeParameter::TL3, FMEnvelopeParameter::KS3, FMEnvelopeParameter::ML3, FMEnvelopeParameter::DT3, FMEnvelopeParameter::AR4, FMEnvelopeParameter::DR4, FMEnvelopeParameter::SR4, FMEnvelopeParameter::RR4, FMEnvelopeParameter::SL4, FMEnvelopeParameter::TL4, FMEnvelopeParameter::KS4, FMEnvelopeParameter::ML4, FMEnvelopeParameter::DT4 }}, { FMOperatorType::Op1, { FMEnvelopeParameter::AL, FMEnvelopeParameter::FB, FMEnvelopeParameter::AR1, FMEnvelopeParameter::DR1, FMEnvelopeParameter::SR1, FMEnvelopeParameter::RR1, FMEnvelopeParameter::SL1, FMEnvelopeParameter::TL1, FMEnvelopeParameter::KS1, FMEnvelopeParameter::ML1, FMEnvelopeParameter::DT1 }}, { FMOperatorType::Op2, { FMEnvelopeParameter::AR2, FMEnvelopeParameter::DR2, FMEnvelopeParameter::SR2, FMEnvelopeParameter::RR2, FMEnvelopeParameter::SL2, FMEnvelopeParameter::TL2, FMEnvelopeParameter::KS2, FMEnvelopeParameter::ML2, FMEnvelopeParameter::DT2 }}, { FMOperatorType::Op3, { FMEnvelopeParameter::AR3, FMEnvelopeParameter::DR3, FMEnvelopeParameter::SR3, FMEnvelopeParameter::RR3, FMEnvelopeParameter::SL3, FMEnvelopeParameter::TL3, FMEnvelopeParameter::KS3, FMEnvelopeParameter::ML3, FMEnvelopeParameter::DT3 }}, { FMOperatorType::Op4, { FMEnvelopeParameter::AR4, FMEnvelopeParameter::DR4, FMEnvelopeParameter::SR4, FMEnvelopeParameter::RR4, FMEnvelopeParameter::SL4, FMEnvelopeParameter::TL4, FMEnvelopeParameter::KS4, FMEnvelopeParameter::ML4, FMEnvelopeParameter::DT4 }} }; std::unique_ptr generateResampler(chip::ResamplerType type) { switch (type) { default: case chip::ResamplerType::BlipBuf: return std::make_unique(false); case chip::ResamplerType::FastBlipBuf: return std::make_unique(true); case chip::ResamplerType::Linear: return std::make_unique(); } } } OPNAController::OPNAController(chip::OpnaEmulator emu, int clock, int rate, int duration, chip::ResamplerType resampler) : mode_(SongType::Standard), storePointADPCM_(0) { constexpr size_t DRAM_SIZE = 262144; // 256KiB opna_ = std::make_unique(emu, clock, rate, duration, DRAM_SIZE, generateResampler(resampler), generateResampler(resampler)); for (size_t inch = 0; inch < 6; ++inch) { fmOpEnables_[inch] = 0xf; for (auto ep : FM_ENV_PARAMS_OP.at(FMOperatorType::All)) opSeqIrtFM_[inch].emplace(ep, nullptr); } for (auto& fm : fm_) fm.isMute = false; for (auto& ssg : ssg_) ssg.isMute = false; for (auto& rhy : rhythm_) rhy.isMute = false; isMuteADPCM_ = false; resetState(); outputHistory_.reset(new int16_t[2 * bt_defs::OUTPUT_HISTORY_SIZE]{}); outputHistoryReady_.reset(new int16_t[2 * bt_defs::OUTPUT_HISTORY_SIZE]{}); outputHistoryIndex_ = 0; } /********** Reset and initialize **********/ void OPNAController::reset() { bool isImmediate = opna_->isImmediateWriteMode(); opna_->setImmediateWriteMode(true); opna_->reset(); resetState(); opna_->setImmediateWriteMode(isImmediate); std::fill(&outputHistory_[0], &outputHistory_[2 * bt_defs::OUTPUT_HISTORY_SIZE], 0); std::fill(&outputHistoryReady_[0], &outputHistoryReady_[2 * bt_defs::OUTPUT_HISTORY_SIZE], 0); } void OPNAController::resetState() { opna_->setRegister(0x29, 0x80); // Init interrupt / YM2608 mode registerDirectSetBuf_.clear(); initFM(); initSSG(); initRhythm(); initADPCM(); } /********** Forward instrument sequence **********/ void OPNAController::tickEvent(SoundSource src, int ch) { switch (src) { case SoundSource::FM: tickEventFM(fm_[ch]); break; case SoundSource::SSG: tickEventSSG(ssg_[ch]); break; case SoundSource::RHYTHM: break; case SoundSource::ADPCM: tickEventADPCM(); break; } } /********** Direct register set **********/ void OPNAController::sendRegister(int address, int value) { registerDirectSetBuf_.push_back({ static_cast(address), static_cast(value) }); } /********** DRAM **********/ size_t OPNAController::getDRAMSize() const { return opna_->getDRAMSize(); } /********** Update register states after tick process **********/ void OPNAController::updateRegisterStates() { updateKeyOnOffStatusRhythm(); // Check direct register set if (!registerDirectSetBuf_.empty()) { for (auto& unit : registerDirectSetBuf_) { opna_->setRegister(unit.address, unit.data); } registerDirectSetBuf_.clear(); } } /********** Real chip interface **********/ void OPNAController::connectToRealChip(RealChipInterfaceType type, RealChipInterfaceGeneratorFunc* f) { opna_->connectToRealChip(type, f); } RealChipInterfaceType OPNAController::getRealChipInterfaceType() const { return opna_->getRealChipInterfaceType(); } bool OPNAController::hasConnectedToRealChip() const { return opna_->hasConnectedToRealChip(); } /********** Stream samples **********/ bool OPNAController::getStreamSamples(int16_t* container, size_t nSamples) { bool result = opna_->mix(container, nSamples); if (!result) return false; size_t nHistory = std::min(nSamples, bt_defs::OUTPUT_HISTORY_SIZE); fillOutputHistory(&container[2 * (nSamples - nHistory)], nHistory); return true; } void OPNAController::getOutputHistory(int16_t* container) { std::lock_guard lock(outputHistoryReadyMutex_); int16_t *history = outputHistoryReady_.get(); std::copy(history, &history[2 * bt_defs::OUTPUT_HISTORY_SIZE], container); } void OPNAController::fillOutputHistory(const int16_t* outputs, size_t nSamples) { int16_t *history = outputHistory_.get(); size_t historyIndex = outputHistoryIndex_; // copy as many as possible to the back size_t backCapacity = bt_defs::OUTPUT_HISTORY_SIZE - historyIndex; size_t nBack = std::min(nSamples, backCapacity); std::copy(outputs, &outputs[2 * nBack], &history[2 * historyIndex]); // copy the rest to the front std::copy(&outputs[2 * nBack], &outputs[2 * nSamples], history); // update the write position historyIndex = (historyIndex + nSamples) % bt_defs::OUTPUT_HISTORY_SIZE; outputHistoryIndex_ = historyIndex; // if no one holds the ready buffer, update the contents std::unique_lock lock(outputHistoryReadyMutex_, std::try_to_lock); if (lock.owns_lock()) { int16_t* dst = outputHistoryReady_.get(); // copy the back, and then the front std::copy(&history[2 * historyIndex], &history[2 * bt_defs::OUTPUT_HISTORY_SIZE], dst); std::copy(&history[0], &history[2 * historyIndex], &dst[2 * (bt_defs::OUTPUT_HISTORY_SIZE - historyIndex)]); } } /********** Chip mode **********/ void OPNAController::setMode(SongType mode) { mode_ = mode; reset(); } void OPNAController::setImmediateWriteMode(bool enabled) noexcept { opna_->setImmediateWriteMode(enabled); } /********** Mute **********/ void OPNAController::setMuteState(SoundSource src, int chInSrc, bool isMute) { switch (src) { case SoundSource::FM: setMuteFMState(chInSrc, isMute); break; case SoundSource::SSG: setMuteSSGState(chInSrc, isMute); break; case SoundSource::RHYTHM: setMuteRhythmState(chInSrc, isMute); break; case SoundSource::ADPCM: setMuteADPCMState(isMute); break; } } bool OPNAController::isMute(SoundSource src, int chInSrc) { switch (src) { case SoundSource::FM: return isMuteFM(chInSrc); case SoundSource::SSG: return isMuteSSG(chInSrc); case SoundSource::RHYTHM: return isMuteRhythm(chInSrc); case SoundSource::ADPCM: return isMuteADPCM(); default: throw std::invalid_argument("Invalid sound source"); } } /********** Stream details **********/ int OPNAController::getRate() const { return opna_->getRate(); } void OPNAController::setRate(int rate) { opna_->setRate(rate); } int OPNAController::getDuration() const { return static_cast(opna_->getMaxDuration()); } void OPNAController::setDuration(int duration) { opna_->setMaxDuration(static_cast(duration)); } void OPNAController::setResampler(chip::ResamplerType type) { opna_->setFmResampler(generateResampler(type)); opna_->setSsgResampler(generateResampler(type)); } void OPNAController::setMasterVolume(int percentage) { opna_->setMasterVolume(percentage); } void OPNAController::setExportContainer(std::shared_ptr cntr) { opna_->setRegisterWriteLogger(cntr); } /********** Internal common process **********/ void OPNAController::checkRealToneByArpeggio(const ArpeggioIterInterface& arpItr, const EchoBuffer& echoBuf, Note& baseNote, bool& shouldSetTone) { Note tmp = baseNote; switch (arpItr->type()) { case SequenceType::AbsoluteSequence: { if (arpItr->hasEnded()) return; Note ln = echoBuf.latest(); ln.addNoteNumber(arpItr->data().data - Note::DEFAULT_NOTE_NUM); baseNote = std::move(ln); break; } case SequenceType::FixedSequence: { // Set original note when an iterator reached the end, otherwise set iterator data baseNote = arpItr->hasEnded() ? echoBuf.latest() : Note(arpItr->data().data); break; } case SequenceType::RelativeSequence: { if (arpItr->hasEnded()) return; baseNote.addNoteNumber(arpItr->data().data - Note::DEFAULT_NOTE_NUM); break; } default: return; } shouldSetTone |= (tmp != baseNote); } void OPNAController::checkPortamento(const ArpeggioIterInterface& arpItr, int prtm, bool hasKeyOnBefore, bool isTonePrtm, EchoBuffer& echoBuf, Note& baseNote, bool& shouldSetTone) { if ((!arpItr || arpItr->hasEnded()) && prtm && hasKeyOnBefore) { if (isTonePrtm) { Note bufNote = echoBuf.latest(); int dif = bufNote.getAbsolutePicth() - baseNote.getAbsolutePicth(); if (dif > 0) { baseNote += std::min(dif, prtm); shouldSetTone = true; } else if (dif < 0) { baseNote += std::max(dif, -prtm); shouldSetTone = true; } } else { baseNote += prtm; shouldSetTone = true; } } } void OPNAController::checkRealToneByPitch(const std::unique_ptr::Iterator>& ptItr, int& sumPitch, bool& shouldSetTone) { if (ptItr->hasEnded()) return; int tmp = sumPitch; switch (ptItr->type()) { case SequenceType::AbsoluteSequence: sumPitch = ptItr->data().data - SEQ_PITCH_CENTER; break; case SequenceType::RelativeSequence: sumPitch += (ptItr->data().data - SEQ_PITCH_CENTER); break; default: return; } shouldSetTone |= (sumPitch != tmp); } //---------- FM ----------// namespace { /// IS_CARRIER[operator][algorithm] constexpr bool IS_CARRIER[4][8] = { { false, false, false, false, false, false, false, true }, { false, false, false, false, true, true, true, true }, { false, false, false, false, false, true, true, true }, { true, true, true, true, true, true, true, true }, }; constexpr uint8_t FM_KEYOFF_MASK[6] = { 0, 1, 2, 4, 5, 6 }; const std::unordered_map FM3_KEY_OFF_MASK = { { 2, 0xe }, { 6, 0xd }, { 7, 0xb }, { 8, 0x7 } }; const std::unordered_map FM3_CH_TO_OP_NUM = { { 2, 0u }, { 6, 1u }, { 7, 2u }, { 8, 3u } }; constexpr int FM3_OP_NUM_TO_CH[4] = { 2, 6, 7, 8 }; constexpr FMEnvelopeParameter PARAM_DT[4] = { FMEnvelopeParameter::DT1, FMEnvelopeParameter::DT2, FMEnvelopeParameter::DT3, FMEnvelopeParameter::DT4 }; constexpr FMEnvelopeParameter PARAM_TL[4] = { FMEnvelopeParameter::TL1, FMEnvelopeParameter::TL2, FMEnvelopeParameter::TL3, FMEnvelopeParameter::TL4 }; constexpr FMEnvelopeParameter PARAM_ML[4] = { FMEnvelopeParameter::ML1, FMEnvelopeParameter::ML2, FMEnvelopeParameter::ML3, FMEnvelopeParameter::ML4 }; constexpr FMEnvelopeParameter PARAM_KS[4] = { FMEnvelopeParameter::KS1, FMEnvelopeParameter::KS2, FMEnvelopeParameter::KS3, FMEnvelopeParameter::KS4 }; constexpr FMEnvelopeParameter PARAM_AR[4] = { FMEnvelopeParameter::AR1, FMEnvelopeParameter::AR2, FMEnvelopeParameter::AR3, FMEnvelopeParameter::AR4 }; constexpr FMEnvelopeParameter PARAM_DR[4] = { FMEnvelopeParameter::DR1, FMEnvelopeParameter::DR2, FMEnvelopeParameter::DR3, FMEnvelopeParameter::DR4 }; constexpr FMEnvelopeParameter PARAM_SR[4] = { FMEnvelopeParameter::SR1, FMEnvelopeParameter::SR2, FMEnvelopeParameter::SR3, FMEnvelopeParameter::SR4 }; constexpr FMEnvelopeParameter PARAM_SL[4] = { FMEnvelopeParameter::SL1, FMEnvelopeParameter::SL2, FMEnvelopeParameter::SL3, FMEnvelopeParameter::SL4 }; constexpr FMEnvelopeParameter PARAM_RR[4] = { FMEnvelopeParameter::RR1, FMEnvelopeParameter::RR2, FMEnvelopeParameter::RR3, FMEnvelopeParameter::RR4 }; constexpr FMEnvelopeParameter PARAM_SSGEG[4] = { FMEnvelopeParameter::SSGEG1, FMEnvelopeParameter::SSGEG2, FMEnvelopeParameter::SSGEG3, FMEnvelopeParameter::SSGEG4 }; constexpr FMLFOParameter PARAM_AM[4] = { FMLFOParameter::AM1, FMLFOParameter::AM2, FMLFOParameter::AM3, FMLFOParameter::AM4 }; constexpr uint32_t OP_OFFSET[4] = { 0u, 8u, 4u, 12u }; uint32_t getFmChannelOffset(size_t inCh) { switch (inCh) { case 0: case 1: case 2: return static_cast(inCh); case 3: case 4: case 5: return static_cast(0x100 + inCh % 3); default: return 0; } } uint32_t getFmChannelOffsetForPitch(size_t ch, size_t inCh, SongType type) { if (type == SongType::FM3chExpanded) { switch (ch) { case 0: case 1: return static_cast(ch); case 3: case 4: case 5: return static_cast(0x100 + ch % 3); case 2: // FM3-OP1 return 9; case 6: // FM3-OP2 return 10; case 7: // FM3-OP3 return 8; case 8: // FM3-OP4 return 2; default: return 0; } } return getFmChannelOffset(inCh); } std::vector getOperatorsInLevel(int level, int al) { switch (level) { case 0: switch (al) { case 0: case 1: case 2: case 3: return { 3 }; case 4: return { 1, 3 }; case 5: case 6: return { 1, 2, 3 }; case 7: return { 0, 1, 2, 3 }; default: throw std::invalid_argument("Invalid algorithm."); } case 1: switch (al) { case 0: case 1: return { 2 }; case 2: return { 0, 2 }; case 3: return { 1, 2 }; case 4: return { 0, 2 }; case 5: case 6: return { 0 }; case 7: return {}; default: throw std::invalid_argument("Invalid algorithm."); } case 2: switch (al) { case 0: return { 1 }; case 1: return { 0, 1 }; case 2: return { 1 }; case 3: return { 0 }; case 4: case 5: case 6: case 7: return {}; default: throw std::invalid_argument("Invalid algorithm."); } case 3: switch (al) { case 0: return { 0 }; case 1: case 2: case 3: case 4: case 5: case 6: case 7: return {}; default: throw std::invalid_argument("Invalid algorithm."); } default: throw std::invalid_argument("Invalid operator level."); } } inline uint8_t judgeSSGEGRegisterValue(int v) { return (v == -1) ? 0 : (0x08 + static_cast(v)); } } /********** Key on-off **********/ void OPNAController::keyOnFM(int ch, const Note& note, bool isJam) { auto& fm = fm_[ch]; if (fm.isMute) return; opna_->setForcedWriteMode(isJam); fm.echoBuf.push(note); bool isTonePrtm = fm.isTonePrtm && fm.hasKeyOnBefore; if (isTonePrtm) { fm.baseNote += (fm.nsSum + fm.transpose); } else { fm.baseNote = fm.echoBuf.latest(); fm.neverSetBaseNote = false; fm.ptSum = 0; fm.volSldSum = 0; } if (fm.oneshotVol != UNUSED_VALUE) { setVolumeFM(ch, fm.baseVol); } if (fm.nsItr && fm.nsItr->state() != SequenceIteratorState::NotBegin) { fm.nsItr.reset(); } fm.shouldSetTone = true; fm.nsSum = 0; fm.transpose = 0; setFrontFMSequences(fm); fm.hasPreSetTickEvent = isJam; if (!isTonePrtm) { uint8_t chdata = FM_KEYOFF_MASK[fm.inCh]; switch (mode_) { case SongType::Standard: { if (fm.isKeyOn) opna_->setRegister(0x28, chdata); // Key off else fm.isKeyOn = true; opna_->setRegister(0x28, static_cast(fmOpEnables_[ch] << 4) | chdata); break; } case SongType::FM3chExpanded: { uint8_t slot = 0; switch (ch) { case 2: case 6: case 7: case 8: { bool prev = fm.isKeyOn; fm.isKeyOn = true; slot = getFM3SlotValidStatus(); if (prev) { // Key off uint8_t flags = static_cast(((slot & FM3_KEY_OFF_MASK.at(ch)) << 4)) | chdata; opna_->setRegister(0x28, flags); } break; } default: slot = fmOpEnables_[ch]; if (fm.isKeyOn) opna_->setRegister(0x28, chdata); // Key off else fm.isKeyOn = true; break; } opna_->setRegister(0x28, static_cast(slot << 4) | chdata); break; } } } fm.hasKeyOnBefore = true; opna_->setForcedWriteMode(false); } void OPNAController::keyOnFM(int ch, int echoBuf) { auto& fm = fm_[ch]; if (static_cast(echoBuf) < fm.echoBuf.size()) { keyOnFM(ch, fm.echoBuf[echoBuf]); } else { tickEventFM(fm); } } void OPNAController::keyOffFM(int ch, bool isJam) { auto& fm = fm_[ch]; if (!fm.isKeyOn) { tickEventFM(fm); return; } opna_->setForcedWriteMode(isJam); releaseStartFMSequences(fm); fm.hasPreSetTickEvent = isJam; fm.isKeyOn = false; uint8_t chdata = FM_KEYOFF_MASK[fm.inCh]; switch (mode_) { case SongType::Standard: { opna_->setRegister(0x28, chdata); break; } case SongType::FM3chExpanded: { uint8_t slot = (fm.inCh == 2) ? static_cast(getFM3SlotValidStatus() << 4) : 0; opna_->setRegister(0x28, slot | chdata); break; } } opna_->setForcedWriteMode(false); } void OPNAController::retriggerKeyOnFM(int ch, int volDiff) { auto& fm = fm_[ch]; if (!fm.isKeyOn || fm.isMute) return; if (volDiff) { setOneshotVolumeFM(ch, utils::clamp(((fm.oneshotVol == UNUSED_VALUE) ? fm.baseVol : fm.oneshotVol) + volDiff, 0, 127)); } setFrontFMSequences(fm); uint8_t chdata = FM_KEYOFF_MASK[fm.inCh]; switch (mode_) { case SongType::Standard: { if (fm.isKeyOn) opna_->setRegister(0x28, chdata); // Key off else fm.isKeyOn = true; opna_->setRegister(0x28, static_cast(fmOpEnables_[ch] << 4) | chdata); break; } case SongType::FM3chExpanded: { uint8_t slot = 0; switch (ch) { case 2: case 6: case 7: case 8: { bool prev = fm.isKeyOn; fm.isKeyOn = true; slot = getFM3SlotValidStatus(); if (prev) { // Key off uint8_t flags = static_cast(((slot & FM3_KEY_OFF_MASK.at(ch)) << 4)) | chdata; opna_->setRegister(0x28, flags); } break; } default: slot = fmOpEnables_[ch]; if (fm.isKeyOn) opna_->setRegister(0x28, chdata); // Key off else fm.isKeyOn = true; break; } opna_->setRegister(0x28, static_cast(slot << 4) | chdata); break; } } } // Change register only void OPNAController::resetFMChannelEnvelope(int ch) { keyOffFM(ch); auto& fm = fm_[ch]; fm.hasResetEnv = true; if (mode_ == SongType::FM3chExpanded && fm.inCh == 2) { FMEnvelopeParameter rr = PARAM_RR[FM3_CH_TO_OP_NUM.at(ch)]; int prev = envFM_[2]->getParameterValue(rr); writeFMEnveropeParameterToRegister(2, rr, 15); envFM_[2]->setParameterValue(rr, prev); } else { auto& env = envFM_[fm.inCh]; for (const FMEnvelopeParameter& rr : PARAM_RR) { int prev = env->getParameterValue(rr); writeFMEnveropeParameterToRegister(fm.inCh, rr, 15); env->setParameterValue(rr, prev); } } } /********** Set instrument **********/ /// NOTE: inst != nullptr void OPNAController::setInstrumentFM(int ch, std::shared_ptr inst) { auto& fm = fm_[ch]; FMOperatorType opType = fm.opType; size_t inch = fm.inCh; auto& refInst = refInstFM_[inch]; if (!refInst || !refInst->isRegisteredWithManager() || refInst->getNumber() != inst->getNumber()) { refInst = inst; writeFMEnvelopeToRegistersFromInstrument(inch); fmOpEnables_[inch] = static_cast(inst->getOperatorEnabled(0)) | static_cast(inst->getOperatorEnabled(1) << 1) | static_cast(inst->getOperatorEnabled(2) << 2) | static_cast(inst->getOperatorEnabled(3) << 3); } else { if (isFBCtrlFM_[inch]) { isFBCtrlFM_[inch] = false; writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::FB, inst->getEnvelopeParameter(FMEnvelopeParameter::FB)); } for (int op = 0; op < 4; ++op) { if (isTLCtrlFM_[inch][op] || isBrightFM_[inch][op]) { isTLCtrlFM_[inch][op] = false; isBrightFM_[inch][op] = false; FMEnvelopeParameter tl = PARAM_TL[op]; writeFMEnveropeParameterToRegister(inch, tl, inst->getEnvelopeParameter(tl)); } if (isMLCtrlFM_[inch][op]) { isMLCtrlFM_[inch][op] = false; FMEnvelopeParameter ml = PARAM_ML[op]; writeFMEnveropeParameterToRegister(inch, ml, inst->getEnvelopeParameter(ml)); } if (isARCtrlFM_[inch][op]) { isARCtrlFM_[inch][op] = false; FMEnvelopeParameter ar = PARAM_AR[op]; writeFMEnveropeParameterToRegister(inch, ar, inst->getEnvelopeParameter(ar)); } if (isDRCtrlFM_[inch][op]) { isDRCtrlFM_[inch][op] = false; FMEnvelopeParameter dr = PARAM_DR[op]; writeFMEnveropeParameterToRegister(inch, dr, inst->getEnvelopeParameter(dr)); } if (isRRCtrlFM_[inch][op]) { isRRCtrlFM_[inch][op] = false; FMEnvelopeParameter rr = PARAM_RR[op]; writeFMEnveropeParameterToRegister(inch, rr, inst->getEnvelopeParameter(rr)); } } restoreFMEnvelopeFromReset(ch); } if (fm.isKeyOn && lfoStartCntFM_[inch] == UNUSED_VALUE) writeFMLFOAllRegisters(inch); for (auto& p : FM_ENV_PARAMS_OP.at(opType)) { if (inst->getOperatorSequenceEnabled(p)) { opSeqIrtFM_[inch].at(p) = inst->getOperatorSequenceSequenceIterator(p); switch (p) { case FMEnvelopeParameter::FB: isFBCtrlFM_[inch] = false; break; case FMEnvelopeParameter::TL1: isTLCtrlFM_[inch][0] = false; isBrightFM_[inch][0] = false; break; case FMEnvelopeParameter::TL2: isTLCtrlFM_[inch][1] = false; isBrightFM_[inch][1] = false; break; case FMEnvelopeParameter::TL3: isTLCtrlFM_[inch][2] = false; isBrightFM_[inch][2] = false; break; case FMEnvelopeParameter::TL4: isTLCtrlFM_[inch][3] = false; isBrightFM_[inch][3] = false; break; case FMEnvelopeParameter::ML1: isMLCtrlFM_[inch][0] = false; break; case FMEnvelopeParameter::ML2: isMLCtrlFM_[inch][1] = false; break; case FMEnvelopeParameter::ML3: isMLCtrlFM_[inch][2] = false; break; case FMEnvelopeParameter::ML4: isMLCtrlFM_[inch][3] = false; break; case FMEnvelopeParameter::AR1: isARCtrlFM_[inch][0] = false; break; case FMEnvelopeParameter::AR2: isARCtrlFM_[inch][1] = false; break; case FMEnvelopeParameter::AR3: isARCtrlFM_[inch][2] = false; break; case FMEnvelopeParameter::AR4: isARCtrlFM_[inch][3] = false; break; case FMEnvelopeParameter::DR1: isDRCtrlFM_[inch][0] = false; break; case FMEnvelopeParameter::DR2: isDRCtrlFM_[inch][1] = false; break; case FMEnvelopeParameter::DR3: isDRCtrlFM_[inch][2] = false; break; case FMEnvelopeParameter::DR4: isDRCtrlFM_[inch][3] = false; break; case FMEnvelopeParameter::RR1: isRRCtrlFM_[inch][0] = false; break; case FMEnvelopeParameter::RR2: isRRCtrlFM_[inch][1] = false; break; case FMEnvelopeParameter::RR3: isRRCtrlFM_[inch][2] = false; break; case FMEnvelopeParameter::RR4: isRRCtrlFM_[inch][3] = false; break; default: break; } } else { opSeqIrtFM_[inch].at(p).reset(); } } if (!fm.isArpEff) { if (inst->getArpeggioEnabled(opType)) fm.arpItr = inst->getArpeggioSequenceIterator(opType); else fm.arpItr.reset(); } if (inst->getPitchEnabled(opType)) fm.ptItr = inst->getPitchSequenceIterator(opType); else fm.ptItr.reset(); if (inst->getPanEnabled()) panItrFM_[inch] = inst->getPanSequenceIterator(); else { panItrFM_[inch].reset(); } setInstrumentFMProperties(fm); checkLFOUsedByInstrument(); } void OPNAController::updateInstrumentFM(int instNum) { int cnt = static_cast(Song::getFMChannelCount(mode_)); for (int ch = 0; ch < cnt; ++ch) { auto& fm = fm_[ch]; size_t inch = fm.inCh; auto& inst = refInstFM_[inch]; if (inst && inst->isRegisteredWithManager() && inst->getNumber() == instNum) { writeFMEnvelopeToRegistersFromInstrument(inch); if (fm.isKeyOn && lfoStartCntFM_[inch] == UNUSED_VALUE) writeFMLFOAllRegisters(inch); FMOperatorType opType = fm.opType; for (auto& p : FM_ENV_PARAMS_OP.at(opType)) { if (!inst->getOperatorSequenceEnabled(p)) opSeqIrtFM_[inch].at(p).reset(); } if (!inst->getArpeggioEnabled(opType)) fm.arpItr.reset(); if (!inst->getPitchEnabled(opType)) fm.ptItr.reset(); if (!inst->getPanEnabled()) panItrFM_[inch].reset(); setInstrumentFMProperties(fm); } } checkLFOUsedByInstrument(); } void OPNAController::updateInstrumentFMEnvelopeParameter(int envNum, FMEnvelopeParameter param) { for (int ch = 0; ch < 6; ++ch) { if (refInstFM_[ch] && refInstFM_[ch]->getEnvelopeNumber() == envNum) { writeFMEnveropeParameterToRegister(ch, param, refInstFM_[ch]->getEnvelopeParameter(param)); } } } void OPNAController::setInstrumentFMOperatorEnabled(int envNum, int opNum) { int chsize = static_cast(Song::getFMChannelCount(mode_)); for (int ch = 0; ch < chsize; ++ch) { auto& fm = fm_[ch]; size_t inch = fm.inCh; if (refInstFM_[inch] && refInstFM_[inch]->getEnvelopeNumber() == envNum) { bool enabled = refInstFM_[inch]->getOperatorEnabled(opNum); envFM_[inch]->setOperatorEnabled(opNum, enabled); if (enabled) { fmOpEnables_[inch] |= (1 << opNum); } else { fmOpEnables_[inch] &= ~(1 << opNum); } if (fm.isKeyOn) { uint8_t chdata = FM_KEYOFF_MASK[inch]; switch (mode_) { case SongType::Standard: { opna_->setRegister(0x28, static_cast(fmOpEnables_[inch] << 4) | chdata); break; } case SongType::FM3chExpanded: { uint8_t slot = (inch == 2) ? getFM3SlotValidStatus() : fmOpEnables_[inch]; opna_->setRegister(0x28, static_cast(slot << 4) | chdata); break; } } } } } } void OPNAController::updateInstrumentFMLFOParameter(int lfoNum, FMLFOParameter param) { for (int ch = 0; ch < 6; ++ch) { if (refInstFM_[ch] && refInstFM_[ch]->getLFOEnabled() && refInstFM_[ch]->getLFONumber() == lfoNum) { writeFMLFORegister(ch, param); } } } /********** Set volume **********/ void OPNAController::setVolumeFM(int ch, int volume) { auto& fm = fm_[ch]; fm.baseVol = volume; fm.oneshotVol = UNUSED_VALUE; fm.xvolSldSum = 0; if (refInstFM_[fm.inCh]) updateFMVolume(fm); // Change TL } void OPNAController::setOneshotVolumeFM(int ch, int volume) { auto& fm = fm_[ch]; fm.oneshotVol = volume; fm.xvolSldSum = 0; if (refInstFM_[fm.inCh]) updateFMVolume(fm); // Change TL } void OPNAController::updateFMVolume(FMChannel& fm) { size_t inch = fm.inCh; switch (fm.opType) { case FMOperatorType::All: for (const FMEnvelopeParameter& tl : PARAM_TL) writeFMEnveropeParameterToRegister(inch, tl, refInstFM_[inch]->getEnvelopeParameter(tl)); break; case FMOperatorType::Op1: writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::TL1, refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::TL1)); break; case FMOperatorType::Op2: writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::TL2, refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::TL2)); break; case FMOperatorType::Op3: writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::TL3, refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::TL3)); break; case FMOperatorType::Op4: writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::TL4, refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::TL4)); break; } } void OPNAController::setMasterVolumeFM(double dB) { opna_->setVolumeFM(dB); } double OPNAController::getMasterVolumeFM() const { return opna_->getVolumeFM(); } /********** Set effect **********/ void OPNAController::setPanFM(int ch, int value) { auto& fm = fm_[ch]; panItrFM_[fm.inCh].reset(); writePanFM(fm, value); } void OPNAController::setArpeggioEffectFM(int ch, int second, int third) { auto& fm = fm_[ch]; if (second || third) { fm.arpItr = std::make_unique(second, third); fm.isArpEff = true; } else { size_t inch = fm.inCh; if (refInstFM_[inch]) { FMOperatorType op = fm.opType; if (!refInstFM_[inch]->getArpeggioEnabled(op)) fm.arpItr.reset(); else fm.arpItr = refInstFM_[inch]->getArpeggioSequenceIterator(op); } fm.isArpEff = false; } } void OPNAController::setPortamentoEffectFM(int ch, int depth, bool isTonePortamento) { auto& fm = fm_[ch]; fm.prtmDepth = depth; fm.isTonePrtm = depth ? isTonePortamento : false; } void OPNAController::setVibratoEffectFM(int ch, int period, int depth) { auto& fm = fm_[ch]; if (period && depth) fm.vibItr = std::make_unique(period, depth); else fm.vibItr.reset(); } void OPNAController::setTremoloEffectFM(int ch, int period, int depth) { auto& fm = fm_[ch]; if (period && depth) fm.treItr = std::make_unique(period, depth); else fm.treItr.reset(); } void OPNAController::setVolumeSlideFM(int ch, int depth, bool isUp) { fm_[ch].volSld = isUp ? -depth : depth; } void OPNAController::setXVolumeSlideFM(int ch, int factor) { auto& iter = fm_[ch].xVolSldItr; constexpr int CYCLE_COUNT = 2; if (factor) iter = std::make_unique(-factor, CYCLE_COUNT); else iter.reset(); } void OPNAController::setDetuneFM(int ch, int pitch) { auto& fm = fm_[ch]; fm.detune = pitch; fm.shouldSetTone = true; } void OPNAController::setFineDetuneFM(int ch, int pitch) { auto& fm = fm_[ch]; fm.fdetune = pitch; fm.shouldSetTone = true; } void OPNAController::setNoteSlideFM(int ch, int speed, int semitone) { auto& fm = fm_[ch]; if (semitone) { fm.nsItr = std::make_unique(speed, semitone); } else fm.nsItr.reset(); } void OPNAController::setTransposeEffectFM(int ch, int semitone) { auto& fm = fm_[ch]; fm.transpose += (semitone * Note::SEMITONE_PITCH); fm.shouldSetTone = true; } void OPNAController::setFBControlFM(int ch, int value) { size_t inch = fm_[ch].inCh; writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::FB, value); isFBCtrlFM_[inch] = true; opSeqIrtFM_[inch].at(FMEnvelopeParameter::FB).reset(); } void OPNAController::setTLControlFM(int ch, int op, int value) { size_t inch = fm_[ch].inCh; FMEnvelopeParameter param = PARAM_TL[op]; writeFMEnveropeParameterToRegister(inch, param, value); isTLCtrlFM_[inch][op] = true; opSeqIrtFM_[inch].at(param).reset(); } void OPNAController::setMLControlFM(int ch, int op, int value) { size_t inch = fm_[ch].inCh; FMEnvelopeParameter param = PARAM_ML[op]; writeFMEnveropeParameterToRegister(inch, param, value); isMLCtrlFM_[inch][op] = true; opSeqIrtFM_[inch].at(param).reset(); } void OPNAController::setARControlFM(int ch, int op, int value) { size_t inch = fm_[ch].inCh; FMEnvelopeParameter param = PARAM_AR[op]; writeFMEnveropeParameterToRegister(inch, param, value); isARCtrlFM_[inch][op] = true; opSeqIrtFM_[inch].at(param).reset(); } void OPNAController::setDRControlFM(int ch, int op, int value) { size_t inch = fm_[ch].inCh; FMEnvelopeParameter param = PARAM_DR[op]; writeFMEnveropeParameterToRegister(inch, param, value); isDRCtrlFM_[inch][op] = true; opSeqIrtFM_[inch].at(param).reset(); } void OPNAController::setRRControlFM(int ch, int op, int value) { size_t inch = fm_[ch].inCh; FMEnvelopeParameter param = PARAM_RR[op]; writeFMEnveropeParameterToRegister(inch, param, value); isRRCtrlFM_[inch][op] = true; opSeqIrtFM_[inch].at(param).reset(); } void OPNAController::setBrightnessFM(int ch, int value) { size_t inch = fm_[ch].inCh; std::vector ops = getOperatorsInLevel(1, envFM_[inch]->getParameterValue(FMEnvelopeParameter::AL)); for (auto& op : ops) { FMEnvelopeParameter param = PARAM_TL[op]; int v = utils::clamp(envFM_[inch]->getParameterValue(param) + value, 0, 127); writeFMEnveropeParameterToRegister(inch, param, v); isBrightFM_[inch][op] = true; opSeqIrtFM_[inch].at(param).reset(); } } /********** For state retrieve **********/ void OPNAController::haltSequencesFM(int ch) { auto& fm = fm_[ch]; for (auto& p : FM_ENV_PARAMS_OP.at(fm.opType)) { if (auto& itr = opSeqIrtFM_[fm.inCh].at(p)) itr->end(); } if (auto& treItr = fm.treItr) treItr->end(); if (auto& arpItr = fm.arpItr) arpItr->end(); if (auto& ptItr = fm.ptItr) ptItr->end(); if (auto& vibItr = fm.vibItr) vibItr->end(); if (auto& nsItr = fm.nsItr) nsItr->end(); if (auto& panItr = panItrFM_[fm.inCh]) panItr->end(); if (auto& xVolSldItr = fm.xVolSldItr) xVolSldItr->end(); } /********** Chip details **********/ bool OPNAController::isKeyOnFM(int ch) const { return fm_[ch].isKeyOn; } bool OPNAController::isTonePortamentoFM(int ch) const { return fm_[ch].isTonePrtm; } bool OPNAController::enableFMEnvelopeReset(int ch) const { auto& fm = fm_[ch]; return envFM_[fm.inCh] ? fm.isEnabledEnvReset : true; } Note OPNAController::getFMLatestNote(int ch) const { return fm_[ch].echoBuf.latest(); } /***********************************/ void OPNAController::initFM() { lfoFreq_ = UNUSED_VALUE; uint8_t mode = 0; switch (mode_) { case SongType::Standard: mode = 0; break; case SongType::FM3chExpanded: mode = 0x40; break; } opna_->setRegister(0x27, mode); for (size_t inch = 0; inch < 6; ++inch) { // Init envelope envFM_[inch] = std::make_unique(-1); refInstFM_[inch].reset(); // Init pan uint32_t bch = getFmChannelOffset(inch); panStateFM_[inch] = PanType::CENTER; panItrFM_[inch].reset(); opna_->setRegister(0xb4 + bch, 0xc0); // Init sequence for (auto& p : opSeqIrtFM_[inch]) { p.second.reset(); } lfoStartCntFM_[inch] = UNUSED_VALUE; isFBCtrlFM_[inch] = false; for (int op = 0; op < 4; ++op) { isTLCtrlFM_[inch][op] = false; isMLCtrlFM_[inch][op] = false; isARCtrlFM_[inch][op] = false; isDRCtrlFM_[inch][op] = false; isRRCtrlFM_[inch][op] = false; isBrightFM_[inch][op] = false; } } size_t fmch = Song::getFMChannelCount(mode_); for (size_t ch = 0; ch < fmch; ++ch) { auto& fm = fm_[ch]; fm.ch = ch; if (ch < 6) fm.inCh = ch; else if (mode_ == SongType::FM3chExpanded && 6 <= ch && ch < 9) fm.inCh = 2; // Init operators key off fm.isKeyOn = false; fm.hasKeyOnBefore = false; fm.echoBuf.clear(); fm.neverSetBaseNote = true; fm.baseVol = 0; // Init volume fm.oneshotVol = UNUSED_VALUE; fm.isEnabledEnvReset = false; fm.hasResetEnv = false; // Set operator type if (mode_ == SongType::FM3chExpanded && fm.inCh == 2) { switch (fm.ch) { case 2: fm.opType = FMOperatorType::Op1; break; case 6: fm.opType = FMOperatorType::Op2; break; case 7: fm.opType = FMOperatorType::Op3; break; case 8: fm.opType = FMOperatorType::Op4; break; default: throw std::out_of_range("out of range."); } } else { fm.opType = FMOperatorType::All; } // Init sequence fm.hasPreSetTickEvent = false; fm.arpItr.reset(); fm.ptItr.reset(); fm.ptSum = 0; fm.shouldSetTone = false; // Effect fm.isArpEff = false; fm.prtmDepth = 0; fm.isTonePrtm = false; fm.vibItr.reset(); fm.treItr.reset(); fm.volSld = 0; fm.volSldSum = 0; fm.xVolSldItr.reset(); fm.xvolSldSum = 0; fm.detune = 0; fm.fdetune = 0; fm.nsItr.reset(); fm.nsSum = 0; fm.transpose = 0; } } void OPNAController::setMuteFMState(int ch, bool isMute) { auto& fm = fm_[ch]; fm.isMute = isMute; if (isMute) { resetFMChannelEnvelope(ch); } else { size_t inch = fm.inCh; switch (fm.opType) { case FMOperatorType::All: for (const FMEnvelopeParameter& rr : PARAM_RR) writeFMEnveropeParameterToRegister(inch, rr, envFM_[inch]->getParameterValue(rr)); break; case FMOperatorType::Op1: writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::RR1, envFM_[inch]->getParameterValue(FMEnvelopeParameter::RR1)); break; case FMOperatorType::Op2: writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::RR2, envFM_[inch]->getParameterValue(FMEnvelopeParameter::RR2)); break; case FMOperatorType::Op3: writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::RR3, envFM_[inch]->getParameterValue(FMEnvelopeParameter::RR3)); break; case FMOperatorType::Op4: writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::RR4, envFM_[inch]->getParameterValue(FMEnvelopeParameter::RR4)); break; } } } bool OPNAController::isMuteFM(int ch) { return fm_[ch].isMute; } void OPNAController::writeFMEnvelopeToRegistersFromInstrument(size_t inch) { uint32_t bch = getFmChannelOffset(inch); // Bank and channel offset uint8_t data1, data2; int al; auto& inst = refInstFM_[inch]; auto& env = envFM_[inch]; data1 = static_cast(inst->getEnvelopeParameter(FMEnvelopeParameter::FB)); env->setParameterValue(FMEnvelopeParameter::FB, data1); data1 <<= 3; al = inst->getEnvelopeParameter(FMEnvelopeParameter::AL); env->setParameterValue(FMEnvelopeParameter::AL, al); data1 += al; opna_->setRegister(0xb0 + bch, data1); bool isExpandedCh = (mode_ == SongType::FM3chExpanded && inch == 2); for (size_t op = 0; op < 4; ++op) { uint32_t offset = bch + OP_OFFSET[op]; FMEnvelopeParameter dt = PARAM_DT[op]; FMEnvelopeParameter ml = PARAM_ML[op]; data1 = static_cast(inst->getEnvelopeParameter(dt)); env->setParameterValue(dt, data1); data1 <<= 4; data2 = static_cast(inst->getEnvelopeParameter(ml)); env->setParameterValue(ml, data2); data1 |= data2; opna_->setRegister(0x30 + offset, data1); FMEnvelopeParameter tl = PARAM_TL[op]; data1 = static_cast(inst->getEnvelopeParameter(tl)); // Adjust volume if (isExpandedCh) data1 = calculateTL(FM3_OP_NUM_TO_CH[op], data1); else if (IS_CARRIER[op][al]) data1 = calculateTL(inch, data1); env->setParameterValue(tl, data1); opna_->setRegister(0x40 + offset, data1); FMEnvelopeParameter ks = PARAM_KS[op]; FMEnvelopeParameter ar = PARAM_AR[op]; data1 = static_cast(inst->getEnvelopeParameter(ks)); env->setParameterValue(ks, data1); data1 <<= 6; data2 = static_cast(inst->getEnvelopeParameter(ar)); env->setParameterValue(ar, data2); data1 |= data2; opna_->setRegister(0x50 + offset, data1); FMEnvelopeParameter dr = PARAM_DR[op]; data1 = inst->getLFOEnabled() ? static_cast(inst->getLFOParameter(PARAM_AM[op])) : 0; data1 <<= 7; data2 = static_cast(inst->getEnvelopeParameter(dr)); env->setParameterValue(dr, data2); data1 |= data2; opna_->setRegister(0x60 + offset, data1); FMEnvelopeParameter sr = PARAM_SR[op]; data1 = static_cast(inst->getEnvelopeParameter(sr)); env->setParameterValue(sr, data2); opna_->setRegister(0x70 + offset, data1); FMEnvelopeParameter sl = PARAM_SL[op]; FMEnvelopeParameter rr = PARAM_RR[op]; data1 = static_cast(inst->getEnvelopeParameter(sl)); env->setParameterValue(sl, data1); data1 <<= 4; data2 = static_cast(inst->getEnvelopeParameter(rr)); env->setParameterValue(rr, data2); data1 |= data2; opna_->setRegister(0x80 + offset, data1); FMEnvelopeParameter ssgeg = PARAM_SSGEG[op]; int tmp = inst->getEnvelopeParameter(ssgeg); env->setParameterValue(ssgeg, tmp); data1 = judgeSSGEGRegisterValue(tmp); opna_->setRegister(0x90 + offset, data1); } } void OPNAController::writeFMEnveropeParameterToRegister(size_t inch, FMEnvelopeParameter param, int value) { uint32_t bch = getFmChannelOffset(inch); // Bank and channel offset uint8_t data; int tmp; auto& env = envFM_[inch]; env->setParameterValue(param, value); switch (param) { case FMEnvelopeParameter::AL: case FMEnvelopeParameter::FB: data = static_cast(env->getParameterValue(FMEnvelopeParameter::FB) << 3); data += env->getParameterValue(FMEnvelopeParameter::AL); opna_->setRegister(0xb0 + bch, data); break; case FMEnvelopeParameter::DT1: case FMEnvelopeParameter::ML1: data = static_cast(env->getParameterValue(FMEnvelopeParameter::DT1) << 4); data |= env->getParameterValue(FMEnvelopeParameter::ML1); opna_->setRegister(0x30 + bch, data); break; case FMEnvelopeParameter::TL1: data = static_cast(env->getParameterValue(FMEnvelopeParameter::TL1)); // Adjust volume if (mode_ == SongType::FM3chExpanded && inch == 2) { data = calculateTL(2, data); env->setParameterValue(FMEnvelopeParameter::TL1, data); // Update } else if (IS_CARRIER[0][env->getParameterValue(FMEnvelopeParameter::AL)]) { data = calculateTL(inch, data); env->setParameterValue(FMEnvelopeParameter::TL1, data); // Update } opna_->setRegister(0x40 + bch, data); break; case FMEnvelopeParameter::KS1: case FMEnvelopeParameter::AR1: data = static_cast(env->getParameterValue(FMEnvelopeParameter::KS1) << 6); data |= env->getParameterValue(FMEnvelopeParameter::AR1); opna_->setRegister(0x50 + bch, data); break; case FMEnvelopeParameter::DR1: if (refInstFM_[inch] && refInstFM_[inch]->getLFOEnabled()) { data = static_cast(refInstFM_[inch]->getLFOParameter(FMLFOParameter::AM1) << 7); } else { data = 0; } data |= env->getParameterValue(FMEnvelopeParameter::DR1); opna_->setRegister(0x60 + bch, data); break; case FMEnvelopeParameter::SR1: data = static_cast(env->getParameterValue(FMEnvelopeParameter::SR1)); opna_->setRegister(0x70 + bch, data); break; case FMEnvelopeParameter::SL1: case FMEnvelopeParameter::RR1: data = static_cast(env->getParameterValue(FMEnvelopeParameter::SL1) << 4); data |= env->getParameterValue(FMEnvelopeParameter::RR1); opna_->setRegister(0x80 + bch, data); break; case::FMEnvelopeParameter::SSGEG1: tmp = env->getParameterValue(FMEnvelopeParameter::SSGEG1); data = (tmp == -1) ? 0 : static_cast(0x08 + tmp); opna_->setRegister(0x90 + bch, data); break; case FMEnvelopeParameter::DT2: case FMEnvelopeParameter::ML2: data = static_cast(env->getParameterValue(FMEnvelopeParameter::DT2) << 4); data |= env->getParameterValue(FMEnvelopeParameter::ML2); opna_->setRegister(0x30 + bch + 8, data); break; case FMEnvelopeParameter::TL2: data = static_cast(env->getParameterValue(FMEnvelopeParameter::TL2)); // Adjust volume if (mode_ == SongType::FM3chExpanded && inch == 2) { data = calculateTL(6, data); env->setParameterValue(FMEnvelopeParameter::TL2, data); // Update } else if (IS_CARRIER[1][env->getParameterValue(FMEnvelopeParameter::AL)]) { data = calculateTL(inch, data); env->setParameterValue(FMEnvelopeParameter::TL2, data); // Update } opna_->setRegister(0x40 + bch + 8, data); break; case FMEnvelopeParameter::KS2: case FMEnvelopeParameter::AR2: data = static_cast(env->getParameterValue(FMEnvelopeParameter::KS2) << 6); data |= env->getParameterValue(FMEnvelopeParameter::AR2); opna_->setRegister(0x50 + bch + 8, data); break; case FMEnvelopeParameter::DR2: if (refInstFM_[inch] && refInstFM_[inch]->getLFOEnabled()) { data = static_cast(refInstFM_[inch]->getLFOParameter(FMLFOParameter::AM2) << 7); } else { data = 0; } data |= env->getParameterValue(FMEnvelopeParameter::DR2); opna_->setRegister(0x60 + bch + 8, data); break; case FMEnvelopeParameter::SR2: data = static_cast(env->getParameterValue(FMEnvelopeParameter::SR2)); opna_->setRegister(0x70 + bch + 8, data); break; case FMEnvelopeParameter::SL2: case FMEnvelopeParameter::RR2: data = static_cast(env->getParameterValue(FMEnvelopeParameter::SL2) << 4); data |= env->getParameterValue(FMEnvelopeParameter::RR2); opna_->setRegister(0x80 + bch + 8, data); break; case FMEnvelopeParameter::SSGEG2: tmp = env->getParameterValue(FMEnvelopeParameter::SSGEG2); data = (tmp == -1) ? 0 : static_cast(0x08 + tmp); opna_->setRegister(0x90 + bch + 8, data); break; case FMEnvelopeParameter::DT3: case FMEnvelopeParameter::ML3: data = static_cast(env->getParameterValue(FMEnvelopeParameter::DT3) << 4); data |= env->getParameterValue(FMEnvelopeParameter::ML3); opna_->setRegister(0x30 + bch + 4, data); break; case FMEnvelopeParameter::TL3: data = static_cast(env->getParameterValue(FMEnvelopeParameter::TL3)); // Adjust volume if (mode_ == SongType::FM3chExpanded && inch == 2) { data = calculateTL(7, data); env->setParameterValue(FMEnvelopeParameter::TL3, data); // Update } else if (IS_CARRIER[2][env->getParameterValue(FMEnvelopeParameter::AL)]) { data = calculateTL(inch, data); env->setParameterValue(FMEnvelopeParameter::TL3, data); // Update } opna_->setRegister(0x40 + bch + 4, data); break; case FMEnvelopeParameter::KS3: case FMEnvelopeParameter::AR3: data = static_cast(env->getParameterValue(FMEnvelopeParameter::KS3) << 6); data |= env->getParameterValue(FMEnvelopeParameter::AR3); opna_->setRegister(0x50 + bch + 4, data); break; case FMEnvelopeParameter::DR3: if (refInstFM_[inch] && refInstFM_[inch]->getLFOEnabled()) { data = static_cast(refInstFM_[inch]->getLFOParameter(FMLFOParameter::AM3) << 7); } else { data = 0; } data |= env->getParameterValue(FMEnvelopeParameter::DR3); opna_->setRegister(0x60 + bch + 4, data); break; case FMEnvelopeParameter::SR3: data = static_cast(env->getParameterValue(FMEnvelopeParameter::SR3)); opna_->setRegister(0x70 + bch + 4, data); break; case FMEnvelopeParameter::SL3: case FMEnvelopeParameter::RR3: data = static_cast(env->getParameterValue(FMEnvelopeParameter::SL3) << 4); data |= env->getParameterValue(FMEnvelopeParameter::RR3); opna_->setRegister(0x80 + bch + 4, data); break; case FMEnvelopeParameter::SSGEG3: tmp = env->getParameterValue(FMEnvelopeParameter::SSGEG3); data = (tmp == -1) ? 0 : static_cast(0x08 + tmp); opna_->setRegister(0x90 + bch + 4, data); break; case FMEnvelopeParameter::DT4: case FMEnvelopeParameter::ML4: data = static_cast(env->getParameterValue(FMEnvelopeParameter::DT4) << 4); data |= env->getParameterValue(FMEnvelopeParameter::ML4); opna_->setRegister(0x30 + bch + 12, data); break; case FMEnvelopeParameter::TL4: data = static_cast(env->getParameterValue(FMEnvelopeParameter::TL4)); // Adjust volume if (mode_ == SongType::FM3chExpanded && inch == 2) { data = calculateTL(8, data); env->setParameterValue(FMEnvelopeParameter::TL4, data); // Update } else { data = calculateTL(inch, data); env->setParameterValue(FMEnvelopeParameter::TL4, data); // Update } opna_->setRegister(0x40 + bch + 12, data); break; case FMEnvelopeParameter::KS4: case FMEnvelopeParameter::AR4: data = static_cast(env->getParameterValue(FMEnvelopeParameter::KS4) << 6); data |= env->getParameterValue(FMEnvelopeParameter::AR4); opna_->setRegister(0x50 + bch + 12, data); break; case FMEnvelopeParameter::DR4: if (refInstFM_[inch] && refInstFM_[inch]->getLFOEnabled()) { data = static_cast(refInstFM_[inch]->getLFOParameter(FMLFOParameter::AM4) << 7); } else { data = 0; } data |= env->getParameterValue(FMEnvelopeParameter::DR4); opna_->setRegister(0x60 + bch + 12, data); break; case FMEnvelopeParameter::SR4: data = static_cast(env->getParameterValue(FMEnvelopeParameter::SR4)); opna_->setRegister(0x70 + bch + 12, data); break; case FMEnvelopeParameter::SL4: case FMEnvelopeParameter::RR4: data = static_cast(env->getParameterValue(FMEnvelopeParameter::SL4) << 4); data |= env->getParameterValue(FMEnvelopeParameter::RR4); opna_->setRegister(0x80 + bch + 12, data); break; case FMEnvelopeParameter::SSGEG4: tmp = env->getParameterValue(FMEnvelopeParameter::SSGEG4); data = judgeSSGEGRegisterValue(tmp); opna_->setRegister(0x90 + bch + 12, data); break; } } void OPNAController::restoreFMEnvelopeFromReset(int ch) { auto& fm = fm_[ch]; size_t inch = fm.inCh; auto& inst = refInstFM_[inch]; if (fm.hasResetEnv == false || !inst) return; fm.hasResetEnv = false; switch (mode_) { case SongType::Standard: { for (const FMEnvelopeParameter& rr : PARAM_RR) { writeFMEnveropeParameterToRegister(inch, rr, inst->getEnvelopeParameter(rr)); } break; } case SongType::FM3chExpanded: { if (inch == 2) { FMEnvelopeParameter param = PARAM_RR[FM3_CH_TO_OP_NUM.at(ch)]; writeFMEnveropeParameterToRegister(2, param, refInstFM_[2]->getEnvelopeParameter(param)); } else { for (const FMEnvelopeParameter& rr : PARAM_RR) { writeFMEnveropeParameterToRegister(inch, rr, inst->getEnvelopeParameter(rr)); } } break; } } } void OPNAController::writeFMLFOAllRegisters(size_t inch) { if (!refInstFM_[inch]->getLFOEnabled() || lfoStartCntFM_[inch] > 0) { // Clear data uint32_t bch = getFmChannelOffset(inch); // Bank and channel offset opna_->setRegister(0xb4 + bch, static_cast(panStateFM_[inch] << 6)); for (size_t op = 0; op < 4; ++op) opna_->setRegister(0x60 + bch + OP_OFFSET[op], static_cast(envFM_[inch]->getParameterValue(PARAM_DR[op]))); } else { writeFMLFORegister(inch, FMLFOParameter::FREQ); writeFMLFORegister(inch, FMLFOParameter::PMS); writeFMLFORegister(inch, FMLFOParameter::AMS); for (const FMLFOParameter& am : PARAM_AM) writeFMLFORegister(inch, am); lfoStartCntFM_[inch] = UNUSED_VALUE; } } void OPNAController::writeFMLFORegister(size_t inch, FMLFOParameter param) { uint32_t bch = getFmChannelOffset(inch); // Bank and channel offset uint8_t data; switch (param) { case FMLFOParameter::FREQ: lfoFreq_ = refInstFM_[inch]->getLFOParameter(FMLFOParameter::FREQ); opna_->setRegister(0x22, static_cast(lfoFreq_ | (1 << 3))); break; case FMLFOParameter::PMS: case FMLFOParameter::AMS: data = static_cast(panStateFM_[inch] << 6); data |= (refInstFM_[inch]->getLFOParameter(FMLFOParameter::AMS) << 4); data |= refInstFM_[inch]->getLFOParameter(FMLFOParameter::PMS); opna_->setRegister(0xb4 + bch, data); break; case FMLFOParameter::AM1: data = static_cast(refInstFM_[inch]->getLFOParameter(FMLFOParameter::AM1) << 7); data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::DR1); opna_->setRegister(0x60 + bch, data); break; case FMLFOParameter::AM2: data = static_cast(refInstFM_[inch]->getLFOParameter(FMLFOParameter::AM2) << 7); data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::DR2); opna_->setRegister(0x60 + bch + 8, data); break; case FMLFOParameter::AM3: data = static_cast(refInstFM_[inch]->getLFOParameter(FMLFOParameter::AM3) << 7); data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::DR3); opna_->setRegister(0x60 + bch + 4, data); break; case FMLFOParameter::AM4: data = static_cast(refInstFM_[inch]->getLFOParameter(FMLFOParameter::AM4) << 7); data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::DR4); opna_->setRegister(0x60 + bch + 12, data); break; default: break; } } void OPNAController::checkLFOUsedByInstrument() { for (const auto& inst : refInstFM_) { if (inst && inst->getLFOEnabled()) return; } // Turn off if no instrument uses LFO if (lfoFreq_ != UNUSED_VALUE) { lfoFreq_ = UNUSED_VALUE; opna_->setRegister(0x22, 0); } } void OPNAController::setFrontFMSequences(FMChannel& fm) { if (fm.isMute) return; size_t inch = fm.inCh; auto& inst = refInstFM_[inch]; if (inst && inst->getLFOEnabled()) { lfoStartCntFM_[inch] = inst->getLFOParameter(FMLFOParameter::Count); writeFMLFOAllRegisters(inch); } else { lfoStartCntFM_[inch] = UNUSED_VALUE; } checkOperatorSequenceFM(fm, 1); if (auto& xVolSldItr = fm.xVolSldItr) xVolSldItr->next(); if (auto& treItr = fm.treItr) treItr->front(); fm.volSldSum += fm.volSld; checkVolumeEffectFM(fm); checkPanFM(fm, 1); if (auto& arpItr = fm.arpItr) { arpItr->front(); checkRealToneFMByArpeggio(fm); } checkPortamentoFM(fm); if (auto& ptItr = fm.ptItr) { ptItr->front(); checkRealToneFMByPitch(fm); } if (auto& vibItr = fm.vibItr) { vibItr->front(); fm.shouldSetTone = true; } if (auto& nsItr = fm.nsItr) { nsItr->front(); if (nsItr->hasEnded()) { nsItr.reset(); } else { fm.nsSum += nsItr->data().data; fm.shouldSetTone = true; } } writePitchFM(fm); } void OPNAController::releaseStartFMSequences(FMChannel& fm) { if (fm.isMute) return; size_t inch = fm.inCh; if (lfoStartCntFM_[inch] > 0) { --lfoStartCntFM_[inch]; writeFMLFOAllRegisters(inch); } checkOperatorSequenceFM(fm, 2); if (auto& xVolSldItr = fm.xVolSldItr) xVolSldItr->next(); if (auto& treItr = fm.treItr) treItr->release(); fm.volSldSum += fm.volSld; checkVolumeEffectFM(fm); checkPanFM(fm, 2); if (auto& arpItr = fm.arpItr) { arpItr->release(); checkRealToneFMByArpeggio(fm); } checkPortamentoFM(fm); if (auto& ptItr = fm.ptItr) { ptItr->release(); checkRealToneFMByPitch(fm); } if (auto& vibItr = fm.vibItr) { vibItr->release(); fm.shouldSetTone = true; } if (auto& nsItr = fm.nsItr) { nsItr->release(); if (nsItr->hasEnded()) { nsItr.reset(); } else { fm.nsSum += nsItr->data().data; fm.shouldSetTone = true; } } if (fm.shouldSetTone) writePitchFM(fm); } void OPNAController::tickEventFM(FMChannel& fm) { if (fm.hasPreSetTickEvent) { fm.hasPreSetTickEvent = false; } else { if (fm.isMute) return; size_t inch = fm.inCh; if (lfoStartCntFM_[inch] > 0) { --lfoStartCntFM_[inch]; writeFMLFOAllRegisters(inch); } checkOperatorSequenceFM(fm, 0); if (auto& xVolSldItr = fm.xVolSldItr) xVolSldItr->next(); if (auto& treItr = fm.treItr) treItr->next(); fm.volSldSum += fm.volSld; checkVolumeEffectFM(fm); checkPanFM(fm, 0); if (auto& arpItr = fm.arpItr) { arpItr->next(); checkRealToneFMByArpeggio(fm); } checkPortamentoFM(fm); if (auto& ptItr = fm.ptItr) { ptItr->next(); checkRealToneFMByPitch(fm); } if (auto& vibItr = fm.vibItr) { vibItr->next(); fm.shouldSetTone = true; } if (auto& nsItr = fm.nsItr) { nsItr->next(); if (nsItr->hasEnded()) { nsItr.reset(); } else { fm.nsSum += nsItr->data().data; fm.shouldSetTone = true; } } if (fm.shouldSetTone) writePitchFM(fm); } } void OPNAController::checkOperatorSequenceFM(FMChannel& fm, int type) { size_t inch = fm.inCh; for (auto& p : FM_ENV_PARAMS_OP.at(fm.opType)) { if (auto& itr = opSeqIrtFM_[inch].at(p)) { switch (type) { case 0: itr->next(); break; case 1: itr->front(); break; case 2: itr->release(); break; default: throw std::out_of_range("The range of type is 0-2."); } if (!itr->hasEnded()) { int d = itr->data().data; if (d != envFM_[inch]->getParameterValue(p)) { writeFMEnveropeParameterToRegister(inch, p, d); } } } } } void OPNAController::checkVolumeEffectFM(FMChannel& fm) { bool isChangedBase = false; if (auto& xVolSldItr = fm.xVolSldItr) { int xslided = fm.xvolSldSum + xVolSldItr->data().data; int newSum; if (fm.oneshotVol == UNUSED_VALUE) { newSum = utils::clamp(fm.baseVol + xslided, 0, 127) - fm.baseVol; } else { newSum = utils::clamp(fm.oneshotVol + xslided, 0, 127) - fm.oneshotVol; } isChangedBase = (std::exchange(fm.xvolSldSum, newSum) != newSum); } int v = fm.xvolSldSum; if (auto& treItr = fm.treItr) { v += treItr->data().data + fm.volSldSum; } else { if (fm.volSld) v += fm.volSldSum; else if (!isChangedBase) return; } uint32_t bch = getFmChannelOffset(fm.inCh); // Bank and channel offset switch (fm.opType) { case FMOperatorType::All: { int al = envFM_[fm.inCh]->getParameterValue(FMEnvelopeParameter::AL); if (IS_CARRIER[0][al]) { // Operator 1 int data = envFM_[fm.inCh]->getParameterValue(FMEnvelopeParameter::TL1) + v; opna_->setRegister(0x40 + bch, static_cast(utils::clamp(data, 0 ,127))); } if (IS_CARRIER[1][al]) { // Operator 2 int data = envFM_[fm.inCh]->getParameterValue(FMEnvelopeParameter::TL2) + v; opna_->setRegister(0x40 + bch + 8, static_cast(utils::clamp(data, 0 ,127))); } if (IS_CARRIER[2][al]) { // Operator 3 int data = envFM_[fm.inCh]->getParameterValue(FMEnvelopeParameter::TL3) + v; opna_->setRegister(0x40 + bch + 4, static_cast(utils::clamp(data, 0 ,127))); } { // Operator 4 (absolutely carrier) int data = envFM_[fm.inCh]->getParameterValue(FMEnvelopeParameter::TL4) + v; opna_->setRegister(0x40 + bch + 12, static_cast(utils::clamp(data, 0 ,127))); } break; } case FMOperatorType::Op1: { int data = envFM_[fm.inCh]->getParameterValue(FMEnvelopeParameter::TL1) + v; opna_->setRegister(0x40 + bch, static_cast(utils::clamp(data, 0 ,127))); break; } case FMOperatorType::Op2: { int data = envFM_[fm.inCh]->getParameterValue(FMEnvelopeParameter::TL2) + v; opna_->setRegister(0x40 + bch + 8, static_cast(utils::clamp(data, 0 ,127))); break; } case FMOperatorType::Op3: { int data = envFM_[fm.inCh]->getParameterValue(FMEnvelopeParameter::TL3) + v; opna_->setRegister(0x40 + bch + 4, static_cast(utils::clamp(data, 0 ,127))); break; } case FMOperatorType::Op4: { int data = envFM_[fm.inCh]->getParameterValue(FMEnvelopeParameter::TL4) + v; opna_->setRegister(0x40 + bch + 12, static_cast(utils::clamp(data, 0 ,127))); break; } } } void OPNAController::checkPanFM(FMChannel& fm, int type) { if (auto& itr = panItrFM_[fm.inCh]) { switch (type) { case 0: itr->next(); break; case 1: itr->front(); break; case 2: itr->release(); break; default: throw std::out_of_range("The range of type is 0-2."); } if (!itr->hasEnded()) { writePanFM(fm, itr->data().data); } } } void OPNAController::writePanFM(FMChannel& fm, int pos) { uint8_t v = static_cast(pos); if (v == panStateFM_[fm.inCh]) return; panStateFM_[fm.inCh] = v; uint32_t bch = getFmChannelOffset(fm.inCh); // Bank and channel offset uint8_t data = static_cast(v << 6); auto& inst = refInstFM_[fm.inCh]; if (inst && inst->getLFOEnabled()) { data |= (inst->getLFOParameter(FMLFOParameter::AMS) << 4); data |= inst->getLFOParameter(FMLFOParameter::PMS); } opna_->setRegister(0xb4 + bch, data); } void OPNAController::writePitchFM(FMChannel& fm) { if (fm.neverSetBaseNote) return; Note&& note = fm.baseNote + (fm.ptSum + (fm.vibItr ? fm.vibItr->data().data : 0) + fm.detune + fm.nsSum + fm.transpose); uint16_t p = note_utils::calculateFNumber(note.getAbsolutePicth(), fm.fdetune); uint32_t offset = getFmChannelOffsetForPitch(fm.ch, fm.inCh, mode_); opna_->setRegister(0xa4 + offset, p >> 8); opna_->setRegister(0xa0 + offset, p & 0x00ff); fm.shouldSetTone = false; } void OPNAController::setInstrumentFMProperties(FMChannel& fm) { fm.isEnabledEnvReset = refInstFM_[fm.inCh]->getEnvelopeResetEnabled(fm.opType); } uint8_t OPNAController::calculateTL(int ch, uint8_t data) const { auto& fm = fm_[ch]; int v = (fm.oneshotVol == UNUSED_VALUE) ? fm.baseVol : fm.oneshotVol; return (data > 127 - v) ? 127 : static_cast(data + v); } //---------- SSG ----------// namespace { constexpr int AUTO_ENV_SHAPE_TYPE[15] = { 17, 17, 17, 21, 21, 21, 21, 16, 17, 18, 19, 20, 21, 22, 23 }; namespace ToneNoiseState { enum : uint8_t { CLEAR_TN = 0, TONE_TN = 1, NOISE_TN = 2, ALL_TN = 3 }; } inline uint8_t SSGToneFlag(size_t ch) { return (1u << ch); } inline uint8_t SSGNoiseFlag(size_t ch) { return (8u << ch); } } /********** Key on-off **********/ void OPNAController::keyOnSSG(int ch, const Note& note, bool isJam) { auto& ssg = ssg_[ch]; if (ssg.isMute) return; opna_->setForcedWriteMode(isJam); ssg.echoBuf.push(note); if (ssg.isTonePrtm && ssg.hasKeyOnBefore) { ssg.baseNote += (ssg.nsSum + ssg.transpose); } else { ssg.baseNote = ssg.echoBuf.latest(); ssg.neverSetBaseNote = false; ssg.ptSum = 0; ssg.volSldSum = 0; ssg.oneshotVol = UNUSED_VALUE; } if (ssg.nsItr && ssg.nsItr->state() != SequenceIteratorState::NotBegin) { ssg.nsItr.reset(); } ssg.shouldSetTone = true; ssg.nsSum = 0; ssg.transpose = 0; { ssg.isInKeyOnProcess_ = true; setFrontSSGSequences(ssg); ssg.isInKeyOnProcess_ = false; } ssg.shouldSkip1stTickExec = isJam; ssg.isKeyOn = true; ssg.hasKeyOnBefore = true; opna_->setForcedWriteMode(false); } void OPNAController::keyOnSSG(int ch, int echoBuf) { auto& ssg = ssg_[ch]; if (static_cast(echoBuf) < ssg.echoBuf.size()) { keyOnSSG(ch, ssg.echoBuf[echoBuf]); } else { tickEventSSG(ssg); } } void OPNAController::keyOffSSG(int ch, bool isJam, bool forceSilence) { auto& ssg = ssg_[ch]; if (!forceSilence && !ssg.isKeyOn) { tickEventSSG(ssg); return; } opna_->setForcedWriteMode(isJam); releaseStartSSGSequences(ssg, forceSilence); ssg.shouldSkip1stTickExec = isJam; ssg.isKeyOn = false; opna_->setForcedWriteMode(false); } void OPNAController::retriggerKeyOnSSG(int ch, int volDiff) { auto& ssg = ssg_[ch]; if (!ssg.isKeyOn || ssg.isMute) return; if (volDiff) { ssg.oneshotVol = utils::clamp(((ssg.oneshotVol == UNUSED_VALUE) ? ssg.baseVol : ssg.oneshotVol) + volDiff, 0, 15); } { ssg.isInKeyOnProcess_ = true; setFrontSSGSequences(ssg); ssg.isInKeyOnProcess_ = false; } } /********** Set instrument **********/ /// NOTE: inst != nullptr void OPNAController::setInstrumentSSG(int ch, std::shared_ptr inst) { auto& ssg = ssg_[ch]; ssg.refInst = inst; if (inst->getWaveformEnabled()) ssg.wfItr = inst->getWaveformSequenceIterator(); else ssg.wfItr.reset(); if (inst->getToneNoiseEnabled()) ssg.tnItr = inst->getToneNoiseSequenceIterator(); else ssg.tnItr.reset(); if (inst->getEnvelopeEnabled()) ssg.envItr = inst->getEnvelopeSequenceIterator(); else ssg.envItr.reset(); if (!ssg.isArpEff) { if (inst->getArpeggioEnabled()) ssg.arpItr = inst->getArpeggioSequenceIterator(); else ssg.arpItr.reset(); } if (inst->getPitchEnabled()) ssg.ptItr = inst->getPitchSequenceIterator(); else ssg.ptItr.reset(); } void OPNAController::updateInstrumentSSG(int instNum) { for (auto& ssg : ssg_) { if (ssg.refInst && ssg.refInst->isRegisteredWithManager() && ssg.refInst->getNumber() == instNum) { if (!ssg.refInst->getWaveformEnabled()) ssg.wfItr.reset(); if (!ssg.refInst->getToneNoiseEnabled()) ssg.tnItr.reset(); if (!ssg.refInst->getEnvelopeEnabled()) ssg.envItr.reset(); if (!ssg.refInst->getArpeggioEnabled()) ssg.arpItr.reset(); if (!ssg.refInst->getPitchEnabled()) ssg.ptItr.reset(); } } } /********** Set volume **********/ void OPNAController::setVolumeSSG(int ch, int volume) { if (volume < bt_defs::NSTEP_SSG_VOLUME) { auto& ssg = ssg_[ch]; ssg.baseVol = volume; ssg.oneshotVol = UNUSED_VALUE; if (ssg.isKeyOn) ssg.shouldSetEnv = true; } } void OPNAController::setOneshotVolumeSSG(int ch, int volume) { if (volume < bt_defs::NSTEP_SSG_VOLUME) { auto& ssg = ssg_[ch]; ssg.oneshotVol = volume; if (ssg.isKeyOn) ssg.shouldSetEnv = true; } } void OPNAController::setRealVolumeSSG(SSGChannel& ssg) { if (SSGWaveformType::testHardEnvelopeOccupancity(ssg.wfChState.data) || ssg.isHardEnv) { ssg.shouldSetEnv = false; return; } int volume = (ssg.oneshotVol == UNUSED_VALUE) ? ssg.baseVol : ssg.oneshotVol; if (auto& envItr = ssg.envItr) { int d = envItr->data().data; if (0 <= d && d < 16) { volume -= (15 - d); } } if (auto& treItr = ssg.treItr) volume += treItr->data().data; volume += ssg.volSldSum; volume = utils::clamp(volume, 0, 15); opna_->setRegister(0x08 + ssg.ch, static_cast(volume)); ssg.shouldSetEnv = false; } void OPNAController::setMasterVolumeSSG(double dB) { opna_->setVolumeSSG(dB); } double OPNAController::getMasterVolumeSSG() const { return opna_->getVolumeSSG(); } /********** Set effect **********/ void OPNAController::setArpeggioEffectSSG(int ch, int second, int third) { auto& ssg = ssg_[ch]; if (second || third) { ssg.arpItr = std::make_unique(second, third); ssg.isArpEff = true; } else { if (!ssg.refInst || !ssg.refInst->getArpeggioEnabled()) ssg.arpItr.reset(); else ssg.arpItr = ssg.refInst->getArpeggioSequenceIterator(); ssg.isArpEff = false; } } void OPNAController::setPortamentoEffectSSG(int ch, int depth, bool isTonePortamento) { auto& ssg = ssg_[ch]; ssg.prtmDepth = depth; ssg.isTonePrtm = depth ? isTonePortamento : false; } void OPNAController::setVibratoEffectSSG(int ch, int period, int depth) { auto& ssg = ssg_[ch]; if (period && depth) ssg.vibItr = std::make_unique(period, depth); else ssg.vibItr.reset(); } void OPNAController::setTremoloEffectSSG(int ch, int period, int depth) { auto& ssg = ssg_[ch]; if (period && depth) ssg.treItr = std::make_unique(period, depth); else ssg.treItr.reset(); } void OPNAController::setVolumeSlideSSG(int ch, int depth, bool isUp) { ssg_[ch].volSld = isUp ? depth : -depth; } void OPNAController::setXVolumeSlideSSG(int ch, int factor) { auto& iter = ssg_[ch].xVolSldItr; constexpr int CYCLE_COUNT = 8; if (factor) iter = std::make_unique(factor, CYCLE_COUNT); else iter.reset(); } void OPNAController::setDetuneSSG(int ch, int pitch) { auto& ssg = ssg_[ch]; ssg.detune = pitch; ssg.shouldSetTone = true; } void OPNAController::setFineDetuneSSG(int ch, int pitch) { auto& ssg = ssg_[ch]; ssg.fdetune = pitch; ssg.shouldSetTone = true; } void OPNAController::setNoteSlideSSG(int ch, int speed, int semitone) { auto& ssg = ssg_[ch]; if (semitone) { ssg.nsItr = std::make_unique(speed, semitone); } else ssg.nsItr.reset(); } void OPNAController::setTransposeEffectSSG(int ch, int semitone) { auto& ssg = ssg_[ch]; ssg.transpose += (semitone * Note::SEMITONE_PITCH); ssg.shouldSetTone = true; } void OPNAController::setToneNoiseMixSSG(int ch, int value) { auto& ssg = ssg_[ch]; // Tone if (value & ToneNoiseState::TONE_TN) mixerSSG_ &= ~SSGToneFlag(ssg.ch); else mixerSSG_ |= SSGToneFlag(ssg.ch); // Noise if (value & ToneNoiseState::NOISE_TN) mixerSSG_ &= ~SSGNoiseFlag(ssg.ch); else mixerSSG_ |= SSGNoiseFlag(ssg.ch); ssg.hasRequestedTnEffSet = true; } void OPNAController::setNoisePitchSSG(int ch, int pitch) { (void)ch; noisePeriodSSG_ = 31 - static_cast(pitch); // Reverse order opna_->setRegister(0x06, noisePeriodSSG_); } void OPNAController::setHardEnvelopePeriod(int ch, bool high, int period) { auto& ssg = ssg_[ch]; bool sendable = ssg.isHardEnv && (ssg.envState.type == SSGEnvelopeUnit::RawSubdata); if (high) { hardEnvPeriodHighSSG_ = period; if (sendable) { int sub = (period << 8) | (ssg.envState.subdata & 0x00ff); ssg.envState = SSGEnvelopeUnit::makeRawUnit(ssg.envState.data, sub); opna_->setRegister(0x0c, static_cast(period)); } } else { hardEnvPeriodLowSSG_ = period; if (sendable) { int sub = (ssg.envState.subdata & 0xff00) | period; ssg.envState = SSGEnvelopeUnit::makeRawUnit(ssg.envState.data, sub); opna_->setRegister(0x0b, static_cast(period)); } } } void OPNAController::setAutoEnvelopeSSG(int ch, int shift, int shape) { auto& ssg = ssg_[ch]; if (shape) { int d = AUTO_ENV_SHAPE_TYPE[shape - 1]; if (!ssg.isMute) opna_->setRegister(0x08 + ssg.ch, 0x10); ssg.isHardEnv = true; if (shift == -8) { // Raw ssg.envState = SSGEnvelopeUnit::makeRawUnit(d, (hardEnvPeriodHighSSG_ << 8) | hardEnvPeriodLowSSG_); opna_->setRegister(0x0c, static_cast(hardEnvPeriodHighSSG_)); opna_->setRegister(0x0b, static_cast(hardEnvPeriodLowSSG_)); ssg.shouldSetEnv = false; ssg.shouldSetHardEnvFreq = false; } else { ssg.envState = SSGEnvelopeUnit::makeShiftUnit(d, shift); ssg.shouldSetEnv = true; ssg.shouldSetHardEnvFreq = true; } // Reset phase opna_->setRegister(0x0d, static_cast(shape)); } else { ssg.isHardEnv = false; ssg.envState = SSGEnvelopeUnit(); // Clear hard envelope in setRealVolumeSSG ssg.shouldSetEnv = true; ssg.shouldSetHardEnvFreq = false; } ssg.envItr.reset(); } void OPNAController::setNoteCutSSG(int ch) { keyOffSSG(ch, false, true); } /********** For state retrieve **********/ void OPNAController::haltSequencesSSG(int ch) { auto& ssg = ssg_[ch]; if (auto& wfItr = ssg.wfItr) wfItr->end(); if (auto& treItr = ssg.treItr) treItr->end(); if (auto& envItr = ssg.envItr) envItr->end(); if (auto& tnItr = ssg.tnItr) tnItr->end(); if (auto& arpItr = ssg.arpItr) arpItr->end(); if (auto& ptItr = ssg.ptItr) ptItr->end(); if (auto& vibItr = ssg.vibItr) vibItr->end(); if (auto& nsItr = ssg.nsItr) nsItr->end(); if (auto& xVolSldItr = ssg.xVolSldItr) xVolSldItr->end(); } /********** Chip details **********/ bool OPNAController::isKeyOnSSG(int ch) const { return ssg_[ch].isKeyOn; } bool OPNAController::isTonePortamentoSSG(int ch) const { return ssg_[ch].isTonePrtm; } Note OPNAController::getSSGLatestNote(int ch) const { return ssg_[ch].echoBuf.latest(); } /***********************************/ void OPNAController::initSSG() { mixerSSG_ = 0xff; opna_->setRegister(0x07, mixerSSG_); noisePeriodSSG_ = 0; opna_->setRegister(0x06, noisePeriodSSG_); hardEnvPeriodHighSSG_ = 0; hardEnvPeriodLowSSG_ = 0; for (size_t ch = 0; ch < 3; ++ch) { auto& ssg = ssg_[ch]; ssg.ch = ch; ssg.isKeyOn = false; ssg.hasKeyOnBefore = false; ssg.isInKeyOnProcess_ = false; ssg.refInst.reset(); // Init envelope ssg.echoBuf.clear(); ssg.neverSetBaseNote = true; ssg.baseVol = bt_defs::NSTEP_SSG_VOLUME - 1; // Init volume ssg.oneshotVol = UNUSED_VALUE; ssg.isHardEnv = false; // Init sequence ssg.shouldSkip1stTickExec = false; ssg.wfItr.reset(); ssg.wfChState = SSGWaveformUnit::makeOnlyDataUnit(SSGWaveformType::UNSET); ssg.envItr.reset(); ssg.envState = SSGEnvelopeUnit(); ssg.tnItr.reset(); ssg.arpItr.reset(); ssg.ptItr.reset(); ssg.ptSum = 0; ssg.shouldSetEnv = false; ssg.shouldSetSqMaskFreq = false; ssg.shouldSetHardEnvFreq = false; ssg.shouldUpdateMixState = false; ssg.shouldSetTone = false; // Effect ssg.isArpEff = false; ssg.prtmDepth = 0; ssg.isTonePrtm = false; ssg.vibItr.reset(); ssg.treItr.reset(); ssg.volSld = 0; ssg.volSldSum = 0; ssg.xVolSldItr.reset(); ssg.detune = 0; ssg.fdetune = 0; ssg.nsItr.reset(); ssg.nsSum = 0; ssg.transpose = 0; ssg.hasRequestedTnEffSet = false; } } void OPNAController::setMuteSSGState(int ch, bool isMute) { auto& ssg = ssg_[ch]; ssg.isMute = isMute; if (isMute) { opna_->setRegister(0x08 + ssg.ch, 0); ssg.isKeyOn = false; } } bool OPNAController::isMuteSSG(int ch) { return ssg_[ch].isMute; } void OPNAController::setFrontSSGSequences(SSGChannel& ssg) { if (ssg.isMute) return; if (auto& wfItr = ssg.wfItr) { wfItr->front(); writeWaveformSSGToRegister(ssg); } else writeSquareWaveform(ssg); if (auto& treItr = ssg.treItr) { treItr->front(); ssg.shouldSetEnv = true; } if (ssg.volSld) { ssg.volSldSum += ssg.volSld; ssg.shouldSetEnv = true; } if (auto& xVolSldItr = ssg.xVolSldItr) { xVolSldItr->next(); xslideVolumeSsg(ssg); } if (auto& envItr = ssg.envItr) { envItr->front(); writeEnvelopeSSGToRegister(ssg); } else setRealVolumeSSG(ssg); if (ssg.hasRequestedTnEffSet) { // Reflect mixer effect writeMixerSSGToRegisterByEffect(ssg); } else if (auto& tnItr = ssg.tnItr) { tnItr->front(); writeMixerSSGToRegisterBySequence(ssg); } else if (ssg.shouldUpdateMixState) writeMixerSSGToRegisterByNoReference(ssg); if (auto& arpItr = ssg.arpItr) { arpItr->front(); checkRealToneSSGByArpeggio(ssg); } checkPortamentoSSG(ssg); if (auto& ptItr = ssg.ptItr) { ptItr->front(); checkRealToneSSGByPitch(ssg); } if (auto& vibItr = ssg.vibItr) { vibItr->front(); ssg.shouldSetTone = true; } if (auto& nsItr = ssg.nsItr) { nsItr->front(); if (!nsItr->hasEnded()) { ssg.nsSum += nsItr->data().data; ssg.shouldSetTone = true; } } writePitchSSG(ssg); } void OPNAController::releaseStartSSGSequences(SSGChannel& ssg, bool forceSilince) { if (ssg.isMute) return; if (auto& wfItr = ssg.wfItr) { wfItr->release(); writeWaveformSSGToRegister(ssg); } if (auto& treItr = ssg.treItr) { treItr->release(); ssg.shouldSetEnv = true; } if (ssg.volSld) { ssg.volSldSum += ssg.volSld; ssg.shouldSetEnv = true; } if (auto& xVolSldItr = ssg.xVolSldItr) { xVolSldItr->next(); xslideVolumeSsg(ssg); } if (auto& envItr = ssg.envItr) { envItr->release(); if (forceSilince || envItr->hasEnded()) { // Silence envItr->end(); opna_->setRegister(0x08 + ssg.ch, 0); ssg.shouldSetEnv = false; ssg.isHardEnv = false; } else writeEnvelopeSSGToRegister(ssg); } else { // Silence opna_->setRegister(0x08 + ssg.ch, 0); ssg.shouldSetEnv = false; ssg.isHardEnv = false; } if (ssg.hasRequestedTnEffSet) { // Reflect mixer effect writeMixerSSGToRegisterByEffect(ssg); } else if (auto& tnItr = ssg.tnItr) { tnItr->release(); writeMixerSSGToRegisterBySequence(ssg); } else if (ssg.shouldUpdateMixState) writeMixerSSGToRegisterByNoReference(ssg); if (auto& arpItr = ssg.arpItr) { arpItr->release(); checkRealToneSSGByArpeggio(ssg); } checkPortamentoSSG(ssg); if (auto& ptItr = ssg.ptItr) { ptItr->release(); checkRealToneSSGByPitch(ssg); } if (auto& vibItr = ssg.vibItr) { vibItr->release(); ssg.shouldSetTone = true; } if (auto& nsItr = ssg.nsItr) { nsItr->release(); if (!nsItr->hasEnded()) { ssg.nsSum += nsItr->data().data; ssg.shouldSetTone = true; } } if (ssg.shouldSetTone || ssg.shouldSetHardEnvFreq || ssg.shouldSetSqMaskFreq) writePitchSSG(ssg); } void OPNAController::tickEventSSG(SSGChannel& ssg) { if (ssg.shouldSkip1stTickExec) { ssg.shouldSkip1stTickExec = false; } else { if (ssg.isMute) return; if (auto& wfItr = ssg.wfItr) { wfItr->next(); writeWaveformSSGToRegister(ssg); } if (auto& treItr = ssg.treItr) { treItr->next(); ssg.shouldSetEnv = true; } if (ssg.volSld) { ssg.volSldSum += ssg.volSld; ssg.shouldSetEnv = true; } if (auto& xVolSldItr = ssg.xVolSldItr) { xVolSldItr->next(); xslideVolumeSsg(ssg); } if (auto& envItr = ssg.envItr) { envItr->next(); writeEnvelopeSSGToRegister(ssg); } else if (ssg.shouldSetEnv) { setRealVolumeSSG(ssg); } if (ssg.hasRequestedTnEffSet) { // Reflect mixer effect writeMixerSSGToRegisterByEffect(ssg); } else if (auto& tnItr = ssg.tnItr) { tnItr->next(); writeMixerSSGToRegisterBySequence(ssg); } else if (ssg.shouldUpdateMixState) writeMixerSSGToRegisterByNoReference(ssg); if (auto& arpItr = ssg.arpItr) { arpItr->next(); checkRealToneSSGByArpeggio(ssg); } checkPortamentoSSG(ssg); if (auto& ptItr = ssg.ptItr) { ptItr->next(); checkRealToneSSGByPitch(ssg); } if (auto& vibItr = ssg.vibItr) { vibItr->next(); ssg.shouldSetTone = true; } if (auto& nsItr = ssg.nsItr) { nsItr->next(); if (!nsItr->hasEnded()) { ssg.nsSum += nsItr->data().data; ssg.shouldSetTone = true; } } if (ssg.shouldSetTone || ssg.shouldSetHardEnvFreq || ssg.shouldSetSqMaskFreq) writePitchSSG(ssg); } } void OPNAController::writeWaveformSSGToRegister(SSGChannel& ssg) { auto& wfItr = ssg.wfItr; if (wfItr->hasEnded()) return; SSGWaveformUnit&& data = wfItr->data(); switch (data.data) { case SSGWaveformType::SQUARE: { writeSquareWaveform(ssg); return; } case SSGWaveformType::TRIANGLE: { if (ssg.wfChState.data == SSGWaveformType::TRIANGLE && !ssg.isInKeyOnProcess_ && ssg.isKeyOn) { ssg.shouldSetEnv = false; return; } switch (ssg.wfChState.data) { case SSGWaveformType::TRIANGLE: case SSGWaveformType::SAW: case SSGWaveformType::INVSAW: break; default: ssg.shouldUpdateMixState = true; break; } // Reset phase switch (ssg.wfChState.data) { case SSGWaveformType::TRIANGLE: case SSGWaveformType::SQM_TRIANGLE: if (ssg.isInKeyOnProcess_) opna_->setRegister(0x0d, 0x0e); break; default: opna_->setRegister(0x0d, 0x0e); break; } if (ssg.isHardEnv) { ssg.isHardEnv = false; } else if (!SSGWaveformType::testHardEnvelopeOccupancity(ssg.wfChState.data) || ssg.isInKeyOnProcess_) { opna_->setRegister(0x08 + ssg.ch, 0x10); } ssg.shouldSetEnv = false; ssg.shouldSetTone = true; ssg.shouldSetSqMaskFreq = false; break; } case SSGWaveformType::SAW: { if (ssg.wfChState.data == SSGWaveformType::SAW && !ssg.isInKeyOnProcess_ && ssg.isKeyOn) { ssg.shouldSetEnv = false; return; } switch (ssg.wfChState.data) { case SSGWaveformType::TRIANGLE: case SSGWaveformType::SAW: case SSGWaveformType::INVSAW: break; default: ssg.shouldUpdateMixState = true; break; } // Reset phase switch (ssg.wfChState.data) { case SSGWaveformType::SAW: case SSGWaveformType::SQM_SAW: if (ssg.isInKeyOnProcess_) opna_->setRegister(0x0d, 0x0c); break; default: opna_->setRegister(0x0d, 0x0c); break; } if (ssg.isHardEnv) { ssg.isHardEnv = false; } else if (!SSGWaveformType::testHardEnvelopeOccupancity(ssg.wfChState.data) || ssg.isInKeyOnProcess_) { opna_->setRegister(0x08 + ssg.ch, 0x10); } ssg.shouldSetEnv = false; ssg.shouldSetTone = true; ssg.shouldSetSqMaskFreq = false; break; } case SSGWaveformType::INVSAW: { if (ssg.wfChState.data == SSGWaveformType::INVSAW && !ssg.isInKeyOnProcess_ && ssg.isKeyOn) { ssg.shouldSetEnv = false; return; } switch (ssg.wfChState.data) { case SSGWaveformType::TRIANGLE: case SSGWaveformType::SAW: case SSGWaveformType::INVSAW: break; default: ssg.shouldUpdateMixState = true; break; } // Reset phase switch (ssg.wfChState.data) { case SSGWaveformType::INVSAW: case SSGWaveformType::SQM_INVSAW: if (ssg.isInKeyOnProcess_) opna_->setRegister(0x0d, 0x08); break; default: opna_->setRegister(0x0d, 0x08); break; } if (ssg.isHardEnv) { ssg.isHardEnv = false; } else if (!SSGWaveformType::testHardEnvelopeOccupancity(ssg.wfChState.data) || ssg.isInKeyOnProcess_) { opna_->setRegister(0x08 + ssg.ch, 0x10); } ssg.shouldSetEnv = false; ssg.shouldSetTone = true; ssg.shouldSetSqMaskFreq = false; break; } case SSGWaveformType::SQM_TRIANGLE: { if (ssg.wfChState == data && !ssg.isInKeyOnProcess_ && ssg.isKeyOn) { ssg.shouldSetEnv = false; return; } switch (ssg.wfChState.data) { case SSGWaveformType::UNSET: case SSGWaveformType::TRIANGLE: case SSGWaveformType::SAW: case SSGWaveformType::INVSAW: ssg.shouldUpdateMixState = true; break; default: break; } if (ssg.wfChState.subdata != data.subdata) { if (data.type == SSGWaveformUnit::RatioSubdata) { // Set frequency of square mask in pitch process since it depends on pitch ssg.shouldSetSqMaskFreq = true; } else { // Raw data uint16_t pitch = static_cast(data.subdata); size_t offset = ssg.ch << 1; opna_->setRegister(0x00 + offset, pitch & 0xff); opna_->setRegister(0x01 + offset, pitch >> 8); ssg.shouldSetSqMaskFreq = false; } } else { ssg.shouldSetSqMaskFreq = false; } // Reset phase switch (ssg.wfChState.data) { case SSGWaveformType::TRIANGLE: case SSGWaveformType::SQM_TRIANGLE: if (ssg.isInKeyOnProcess_) opna_->setRegister(0x0d, 0x0e); break; default: opna_->setRegister(0x0d, 0x0e); break; } if (ssg.isHardEnv) { ssg.isHardEnv = false; } else if (!SSGWaveformType::testHardEnvelopeOccupancity(ssg.wfChState.data) || ssg.isInKeyOnProcess_) { opna_->setRegister(0x08 + ssg.ch, 0x10); } ssg.shouldSetEnv = false; ssg.shouldSetTone = true; break; } case SSGWaveformType::SQM_SAW: { if (ssg.wfChState == data && !ssg.isInKeyOnProcess_ && ssg.isKeyOn) { ssg.shouldSetEnv = false; return; } switch (ssg.wfChState.data) { case SSGWaveformType::UNSET: case SSGWaveformType::TRIANGLE: case SSGWaveformType::SAW: case SSGWaveformType::INVSAW: ssg.shouldUpdateMixState = true; break; default: break; } if (ssg.wfChState.subdata != data.subdata) { if (data.type == SSGWaveformUnit::RatioSubdata) { // Set frequency of square mask in pitch process since it depends on pitch ssg.shouldSetSqMaskFreq = true; } else { // Raw data uint16_t pitch = static_cast(data.subdata); size_t offset = ssg.ch << 1; opna_->setRegister(0x00 + offset, pitch & 0xff); opna_->setRegister(0x01 + offset, pitch >> 8); ssg.shouldSetSqMaskFreq = false; } } else { ssg.shouldSetSqMaskFreq = false; } // Reset phase switch (ssg.wfChState.data) { case SSGWaveformType::SAW: case SSGWaveformType::SQM_SAW: if (ssg.isInKeyOnProcess_) opna_->setRegister(0x0d, 0x0c); break; default: opna_->setRegister(0x0d, 0x0c); break; } if (ssg.isHardEnv) { ssg.isHardEnv = false; } else if (!SSGWaveformType::testHardEnvelopeOccupancity(ssg.wfChState.data) || ssg.isInKeyOnProcess_) { opna_->setRegister(0x08 + ssg.ch, 0x10); } ssg.shouldSetEnv = false; ssg.shouldSetTone = true; break; } case SSGWaveformType::SQM_INVSAW: { if (ssg.wfChState == data && !ssg.isInKeyOnProcess_ && ssg.isKeyOn) { ssg.shouldSetEnv = false; return; } switch (ssg.wfChState.data) { case SSGWaveformType::UNSET: case SSGWaveformType::TRIANGLE: case SSGWaveformType::SAW: case SSGWaveformType::INVSAW: ssg.shouldUpdateMixState = true; break; default: break; } if (ssg.wfChState.subdata != data.subdata) { if (data.type == SSGWaveformUnit::RatioSubdata) { // Set frequency of square mask in pitch process since it depends on pitch ssg.shouldSetSqMaskFreq = true; } else { // Raw data uint16_t pitch = static_cast(data.subdata); size_t offset = ssg.ch << 1; opna_->setRegister(0x00 + offset, pitch & 0xff); opna_->setRegister(0x01 + offset, pitch >> 8); ssg.shouldSetSqMaskFreq = false; } } else { ssg.shouldSetSqMaskFreq = false; } // Reset phase switch (ssg.wfChState.data) { case SSGWaveformType::INVSAW: case SSGWaveformType::SQM_INVSAW: if (ssg.isInKeyOnProcess_) opna_->setRegister(0x0d, 0x08); break; default: opna_->setRegister(0x0d, 0x08); break; } if (ssg.isHardEnv) { ssg.isHardEnv = false; } else if (!SSGWaveformType::testHardEnvelopeOccupancity(ssg.wfChState.data) || ssg.isInKeyOnProcess_) { opna_->setRegister(0x08 + ssg.ch, 0x10); } ssg.shouldSetEnv = false; ssg.shouldSetTone = true; break; } default: return; } ssg.wfChState = std::move(data); // Clear current envelope state // since the register of volume and hardware envelope frequency is used by waveform sequence ssg.envState = SSGEnvelopeUnit(); } void OPNAController::writeSquareWaveform(SSGChannel& ssg) { if (ssg.wfChState.data == SSGWaveformType::SQUARE) { if (ssg.isInKeyOnProcess_) { ssg.shouldSetEnv = true; ssg.shouldSetTone = true; } return; } switch (ssg.wfChState.data) { case SSGWaveformType::SQM_TRIANGLE: case SSGWaveformType::SQM_SAW: case SSGWaveformType::SQM_INVSAW: break; default: ssg.shouldUpdateMixState = true; break; } ssg.shouldSetEnv = true; ssg.shouldSetTone = true; ssg.shouldSetSqMaskFreq = false; ssg.wfChState = SSGWaveformUnit::makeOnlyDataUnit(SSGWaveformType::SQUARE); } void OPNAController::xslideVolumeSsg(SSGChannel& ssg) { bool isChanged; auto slide = [v = ssg.xVolSldItr->data().data](int& target) { return std::exchange(target, utils::clamp(target + v, 0, 15)); }; int prevBase = slide(ssg.baseVol); if (ssg.oneshotVol == UNUSED_VALUE) { isChanged = (ssg.baseVol != prevBase); } else { int prevOneShot = slide(ssg.oneshotVol); isChanged = (ssg.oneshotVol != prevOneShot); } ssg.shouldSetEnv |= isChanged; } void OPNAController::writeEnvelopeSSGToRegister(SSGChannel& ssg) { // Skip if waveform settings use hardware envelope if (SSGWaveformType::testHardEnvelopeOccupancity(ssg.wfChState.data)) return; auto& envItr = ssg.envItr; if (envItr->hasEnded()) { if (ssg.shouldSetEnv) setRealVolumeSSG(ssg); return; } SSGEnvelopeUnit&& data = envItr->data(); if (data.data < 16) { // Software envelope ssg.isHardEnv = false; ssg.envState = std::move(data); setRealVolumeSSG(ssg); } else { // Hardware envelope if (!ssg.isHardEnv) { opna_->setRegister(0x08 + ssg.ch, 0x10); ssg.isHardEnv = true; } if (ssg.envState.subdata != data.subdata) { ssg.envState.type = data.type; ssg.envState.subdata = data.subdata; if (data.type == SSGEnvelopeUnit::RatioSubdata) { // Set frequency of hardware envelope in pitch process since it depends on pitch ssg.shouldSetHardEnvFreq = true; } else { // Raw data opna_->setRegister(0x0b, 0x00ff & ssg.envState.subdata); opna_->setRegister(0x0c, static_cast(ssg.envState.subdata >> 8)); ssg.shouldSetHardEnvFreq = false; } } if (ssg.envState.data != data.data || ssg.isInKeyOnProcess_) { opna_->setRegister(0x0d, static_cast(data.data - 16 + 8)); // Reset phase ssg.envState.data = data.data; if (data.type == SSGEnvelopeUnit::RatioSubdata) { // Set frequency of hardware envelope in pitch process since it depends on pitch ssg.shouldSetHardEnvFreq = true; } } } ssg.shouldSetEnv = false; } void OPNAController::writeMixerSSGToRegisterByEffect(SSGChannel& ssg) { ssg.tnItr.reset(); opna_->setRegister(0x07, mixerSSG_); ssg.hasRequestedTnEffSet = false; ssg.shouldUpdateMixState = false; } void OPNAController::writeMixerSSGToRegisterBySequence(SSGChannel& ssg) { auto& tnItr = ssg.tnItr; if (tnItr->hasEnded()) { if (ssg.shouldUpdateMixState) writeMixerSSGToRegisterByNoReference(ssg); return; } int type = tnItr->data().data; uint8_t prevMixer = mixerSSG_; if (!type) { // tone switch (ssg.wfChState.data) { case SSGWaveformType::TRIANGLE: case SSGWaveformType::SAW: case SSGWaveformType::INVSAW: mixerSSG_ |= SSGToneFlag(ssg.ch); // Off for buzzer effects break; default: mixerSSG_ &= ~SSGToneFlag(ssg.ch); break; } mixerSSG_ |= SSGNoiseFlag(ssg.ch); } else if (type == 65) { // None mixerSSG_ |= SSGToneFlag(ssg.ch); mixerSSG_ |= SSGNoiseFlag(ssg.ch); } else if (type > 32) { // Tone&Noise switch (ssg.wfChState.data) { case SSGWaveformType::TRIANGLE: case SSGWaveformType::SAW: case SSGWaveformType::INVSAW: mixerSSG_ |= SSGToneFlag(ssg.ch); break; default: mixerSSG_ &= ~SSGToneFlag(ssg.ch); break; } mixerSSG_ &= ~SSGNoiseFlag(ssg.ch); uint8_t p = static_cast(64 - type); // Reverse order if (noisePeriodSSG_ != p) { noisePeriodSSG_ = p; opna_->setRegister(0x06, p); } } else { // Noise mixerSSG_ |= SSGToneFlag(ssg.ch); mixerSSG_ &= ~SSGNoiseFlag(ssg.ch); uint8_t p = static_cast(32 - type); // Reverse order if (noisePeriodSSG_ != p) { noisePeriodSSG_ = p; opna_->setRegister(0x06, p); } } if (mixerSSG_ != prevMixer) opna_->setRegister(0x07, mixerSSG_); ssg.shouldUpdateMixState = false; } void OPNAController::writeMixerSSGToRegisterByNoReference(SSGChannel& ssg) { switch (ssg.wfChState.data) { case SSGWaveformType::TRIANGLE: case SSGWaveformType::SAW: case SSGWaveformType::INVSAW: mixerSSG_ |= SSGToneFlag(ssg.ch); break; default: mixerSSG_ &= ~SSGToneFlag(ssg.ch); break; } opna_->setRegister(0x07, mixerSSG_); ssg.shouldUpdateMixState = false; } void OPNAController::writePitchSSG(SSGChannel& ssg) { if (ssg.neverSetBaseNote) return; int p = (ssg.baseNote + (ssg.ptSum + (ssg.vibItr ? ssg.vibItr->data().data : 0) + ssg.detune + ssg.nsSum + ssg.transpose)).getAbsolutePicth(); switch (ssg.wfChState.data) { case SSGWaveformType::SQUARE: { uint16_t pitch = note_utils::calculateSSGSquareTP(p, ssg.fdetune); if (ssg.shouldSetTone) { size_t offset = ssg.ch << 1; opna_->setRegister(0x00 + offset, pitch & 0xff); opna_->setRegister(0x01 + offset, pitch >> 8); // Forced call in case of changes in tone processing writeAutoEnvelopePitchSSG(ssg, pitch); } else if (ssg.shouldSetHardEnvFreq) { writeAutoEnvelopePitchSSG(ssg, pitch); } break; } case SSGWaveformType::TRIANGLE: if (ssg.shouldSetTone) { uint16_t pitch = note_utils::calculateSSGTriangleEP(p, ssg.fdetune); opna_->setRegister(0x0b, pitch & 0x00ff); opna_->setRegister(0x0c, pitch >> 8); } break; case SSGWaveformType::SAW: case SSGWaveformType::INVSAW: if (ssg.shouldSetTone){ uint16_t pitch = note_utils::calculateSSGSawEP(p, ssg.fdetune); opna_->setRegister(0x0b, pitch & 0x00ff); opna_->setRegister(0x0c, pitch >> 8); } break; case SSGWaveformType::SQM_TRIANGLE: { uint16_t pitch = note_utils::calculateSSGTriangleEP(p, ssg.fdetune); if (ssg.shouldSetTone) { opna_->setRegister(0x0b, pitch & 0x00ff); opna_->setRegister(0x0c, pitch >> 8); // Forced call in case of changes in tone processing if (ssg.wfChState.type == SSGWaveformUnit::RatioSubdata) { writeSquareMaskPitchSSG(ssg, pitch, true); } } else if (ssg.shouldSetSqMaskFreq) { if (ssg.wfChState.type == SSGWaveformUnit::RatioSubdata) { writeSquareMaskPitchSSG(ssg, pitch, true); } } break; } case SSGWaveformType::SQM_SAW: case SSGWaveformType::SQM_INVSAW: { uint16_t pitch = note_utils::calculateSSGSawEP(p, ssg.fdetune); if (ssg.shouldSetTone) { opna_->setRegister(0x0b, pitch & 0x00ff); opna_->setRegister(0x0c, pitch >> 8); // Forced call in case of changes in tone processing if (ssg.wfChState.type == SSGWaveformUnit::RatioSubdata) { writeSquareMaskPitchSSG(ssg, pitch, false); } } else if (ssg.shouldSetSqMaskFreq) { if (ssg.wfChState.type == SSGWaveformUnit::RatioSubdata) { writeSquareMaskPitchSSG(ssg, pitch, false); } } break; } default: break; } ssg.shouldSetTone = false; ssg.shouldSetEnv = false; ssg.shouldSetHardEnvFreq = false; ssg.shouldSetSqMaskFreq = false; } void OPNAController::writeAutoEnvelopePitchSSG(SSGChannel& ssg, double tonePitch) { // Multiple frequency if triangle int div = (ssg.envState.data == 18 || ssg.envState.data == 22) ? 32 : 16; switch (ssg.envState.type) { case SSGEnvelopeUnit::RatioSubdata: { int r1, r2; ssg.envState.getSubdataAsRatio(r1, r2); uint16_t period = static_cast(std::round(tonePitch * r1 / (r2 * div))); opna_->setRegister(0x0b, 0x00ff & period); opna_->setRegister(0x0c, static_cast(period >> 8)); break; } case SSGEnvelopeUnit::ShiftSubdata: { uint16_t period = static_cast(std::round(tonePitch / div)); int rshift; ssg.envState.getSubdataAsShift(rshift); rshift -= 4; // Adjust rate to that of 0CC-FamiTracker if (rshift < 0) period <<= -rshift; else period >>= rshift; opna_->setRegister(0x0b, 0x00ff & period); opna_->setRegister(0x0c, static_cast(period >> 8)); break; } default: break; } } void OPNAController::writeSquareMaskPitchSSG(SSGChannel& ssg, double tonePitch, bool isTriangle) { int mul = isTriangle ? 32 : 16; // Multiple frequency if triangle int r1, r2; ssg.wfChState.getSubdataAsRatio(r1, r2); // Calculate mask period uint16_t period = static_cast(std::round(r1 * mul * tonePitch / r2)); size_t offset = ssg.ch << 1; opna_->setRegister(0x00 + offset, period & 0x00ff); opna_->setRegister(0x01 + offset, period >> 8); } //---------- Rhythm ----------// namespace { inline uint8_t makePanAndVolumeRegVal(uint8_t panState, int volume) { return static_cast((panState << 6) | volume); } } /********** Key on/off **********/ void OPNAController::setKeyOnFlagRhythm(int ch) { auto& rhy = rhythm_[ch]; if (rhy.isMute) return; if (rhy.oneshotVol != UNUSED_VALUE) setVolumeRhythm(ch, rhy.baseVol); keyOnRequestFlagsRhythm_ |= static_cast(1 << ch); } void OPNAController::setKeyOffFlagRhythm(int ch) { keyOffRequestFlagsRhythm_ |= static_cast(1 << ch); } void OPNAController::retriggerKeyOnFlagRhythm(int ch, int volDiff) { auto& rhy = rhythm_[ch]; if (rhy.isMute) return; if (volDiff) { setOneshotVolumeRhythm(ch, utils::clamp(((rhy.oneshotVol == UNUSED_VALUE) ? rhy.baseVol : rhy.oneshotVol) + volDiff, 0, 31)); } keyOnRequestFlagsRhythm_ |= static_cast(1 << ch); } /********** Set volume **********/ void OPNAController::setVolumeRhythm(int ch, int volume) { if (volume < bt_defs::NSTEP_RHYTHM_VOLUME) { auto& rhy = rhythm_[ch]; rhy.baseVol = volume; rhy.oneshotVol = UNUSED_VALUE; opna_->setRegister(0x18 + static_cast(ch), makePanAndVolumeRegVal(rhy.panState, volume)); } } void OPNAController::setOneshotVolumeRhythm(int ch, int volume) { if (volume < bt_defs::NSTEP_RHYTHM_VOLUME) { auto& rhy = rhythm_[ch]; rhy.oneshotVol = volume; opna_->setRegister(0x18 + static_cast(ch), makePanAndVolumeRegVal(rhy.panState, volume)); } } void OPNAController::setMasterVolumeRhythm(int volume) { masterVolRhythm_ = volume; opna_->setRegister(0x11, static_cast(volume)); } /********** Set effect **********/ void OPNAController::setPanRhythm(int ch, int value) { auto& rhy = rhythm_[ch]; rhy.panState = static_cast(value); int volume = (rhy.oneshotVol == UNUSED_VALUE) ? rhy.baseVol : rhy.oneshotVol; opna_->setRegister(0x18 + static_cast(ch), makePanAndVolumeRegVal(value, volume)); } /***********************************/ void OPNAController::initRhythm() { keyOnRequestFlagsRhythm_ = 0; keyOffRequestFlagsRhythm_ = 0; masterVolRhythm_ = 0x3f; opna_->setRegister(0x11, 0x3f); // Rhythm total volume for (size_t ch = 0; ch < 6; ++ch) { auto& rhy = rhythm_[ch]; rhy.baseVol = bt_defs::NSTEP_RHYTHM_VOLUME - 1; rhy.oneshotVol = UNUSED_VALUE; // Init pan rhy.panState = 3; opna_->setRegister(0x18 + ch, 0xdf); } } void OPNAController::setMuteRhythmState(int ch, bool isMute) { rhythm_[ch].isMute = isMute; if (isMute) { setKeyOffFlagRhythm(ch); updateKeyOnOffStatusRhythm(); } } bool OPNAController::isMuteRhythm(int ch) { return rhythm_[ch].isMute; } void OPNAController::updateKeyOnOffStatusRhythm(bool isJam) { opna_->setForcedWriteMode(isJam); if (keyOnRequestFlagsRhythm_) { opna_->setRegister(0x10, keyOnRequestFlagsRhythm_); keyOnRequestFlagsRhythm_ = 0; } if (keyOffRequestFlagsRhythm_) { opna_->setRegister(0x10, 0x80 | keyOffRequestFlagsRhythm_); keyOffRequestFlagsRhythm_ = 0; } opna_->setForcedWriteMode(false); } //---------- ADPCM ----------// namespace { int calculateCurrentKeyOfDrumkit(int noteNum, int transpose) { return utils::clamp(noteNum + transpose / Note::SEMITONE_PITCH, 0, Note::NOTE_NUMBER_RANGE - 1); } } /********** Key on-off **********/ void OPNAController::keyOnADPCM(const Note& note, bool isJam) { if (isMuteADPCM_ || (!refInstADPCM_ && !refInstKit_)) return; opna_->setForcedWriteMode(isJam); echoBufADPCM_.push(note); bool isTonePrtm = hasTonePrtmADPCM_ && hasKeyOnBeforeADPCM_; if (isTonePrtm) { baseNoteADPCM_ += (nsSumADPCM_ + transposeADPCM_); } else { baseNoteADPCM_ = echoBufADPCM_.latest(); neverSetBaseNoteADPCM_ = false; ptSumADPCM_ = 0; volSldSumADPCM_ = 0; oneshotVolADPCM_ = UNUSED_VALUE; } if (nsItrADPCM_ && nsItrADPCM_->state() != SequenceIteratorState::NotBegin) { nsItrADPCM_.reset(); } shouldSetToneADPCM_ = true; nsSumADPCM_ = 0; transposeADPCM_ = 0; setFrontADPCMSequences(); shouldSkip1stTickExecADPCM_ = isJam; if (!isTonePrtm) { opna_->setRegister(0x101, 0x02); opna_->setRegister(0x100, 0xa1); if (refInstADPCM_) { SampleRepeatFlag flag = refInstADPCM_->getSampleRepeatFlag(); size_t startAddr = refInstADPCM_->getSampleStartAddress(); SampleRepeatRange range = refInstADPCM_->getSampleRepeatRange(); if (flag & SampleRepeatFlag::ShouldRewriteStop) { triggerSamplePlayADPCM(startAddr, startAddr + range.last(), refInstADPCM_->isSampleRepeatable()); } else { triggerSamplePlayADPCM(startAddr, refInstADPCM_->getSampleStopAddress(), refInstADPCM_->isSampleRepeatable()); } if (flag & SampleRepeatFlag::ShouldRewriteStart) { repeatAddrADPCM_.push_back(startAddr + range.first()); } } else if (hasStartRequestedKit_) { // valid key in refInstKit_ int key = baseNoteADPCM_.getNoteNumber(); SampleRepeatFlag flag = refInstKit_->getSampleRepeatFlag(key); size_t startAddr = refInstKit_->getSampleStartAddress(key); SampleRepeatRange range = refInstKit_->getSampleRepeatRange(key); if (flag & SampleRepeatFlag::ShouldRewriteStop) { triggerSamplePlayADPCM(startAddr, startAddr + range.last(), refInstKit_->isSampleRepeatable(key)); } else { triggerSamplePlayADPCM(startAddr, refInstKit_->getSampleStopAddress(key), refInstKit_->isSampleRepeatable(key)); } if (flag & SampleRepeatFlag::ShouldRewriteStart) { repeatAddrADPCM_.push_back(startAddr + range.first()); } hasStartRequestedKit_ = false; } isKeyOnADPCM_ = true; } hasKeyOnBeforeADPCM_ = true; opna_->setForcedWriteMode(false); } void OPNAController::keyOnADPCM(int echoBuf) { if (static_cast(echoBuf) < echoBufADPCM_.size()) { keyOnADPCM(echoBufADPCM_[echoBuf]); } else { tickEventADPCM(); } } void OPNAController::keyOffADPCM(bool isJam, bool forceSilence) { if (!forceSilence && !isKeyOnADPCM_) { tickEventADPCM(); return; } opna_->setForcedWriteMode(isJam); releaseStartADPCMSequences(forceSilence); shouldSkip1stTickExecADPCM_ = isJam; isKeyOnADPCM_ = false; opna_->setForcedWriteMode(false); } void OPNAController::retriggerKeyOnADPCM(int volDiff) { if (!isKeyOnADPCM_ || isMuteADPCM_ || (!refInstADPCM_ && !refInstKit_)) return; if (volDiff) { oneshotVolADPCM_ = utils::clamp(((oneshotVolADPCM_ == UNUSED_VALUE) ? baseVolADPCM_ : oneshotVolADPCM_) + volDiff, 0, 255); } setFrontADPCMSequences(); opna_->setRegister(0x101, 0x02); opna_->setRegister(0x100, 0xa1); if (refInstADPCM_) { SampleRepeatFlag flag = refInstADPCM_->getSampleRepeatFlag(); size_t startAddr = refInstADPCM_->getSampleStartAddress(); SampleRepeatRange range = refInstADPCM_->getSampleRepeatRange(); if (flag & SampleRepeatFlag::ShouldRewriteStop) { triggerSamplePlayADPCM(startAddr, startAddr + range.last(), refInstADPCM_->isSampleRepeatable()); } else { triggerSamplePlayADPCM(startAddr, refInstADPCM_->getSampleStopAddress(), refInstADPCM_->isSampleRepeatable()); } if (flag & SampleRepeatFlag::ShouldRewriteStart) { repeatAddrADPCM_.push_back(startAddr + range.first()); } } else if (hasStartRequestedKit_) { // valid key in refInstKit_ int key = baseNoteADPCM_.getNoteNumber(); SampleRepeatFlag flag = refInstKit_->getSampleRepeatFlag(key); size_t startAddr = refInstKit_->getSampleStartAddress(key); SampleRepeatRange range = refInstKit_->getSampleRepeatRange(key); if (flag & SampleRepeatFlag::ShouldRewriteStop) { triggerSamplePlayADPCM(startAddr, startAddr + range.last(), refInstKit_->isSampleRepeatable(key)); } else { triggerSamplePlayADPCM(startAddr, refInstKit_->getSampleStopAddress(key), refInstKit_->isSampleRepeatable(key)); } if (flag & SampleRepeatFlag::ShouldRewriteStart) { repeatAddrADPCM_.push_back(startAddr + range.first()); } hasStartRequestedKit_ = false; } } /********** Set instrument **********/ /// NOTE: inst != nullptr void OPNAController::setInstrumentADPCM(std::shared_ptr inst) { refInstADPCM_ = inst; refInstKit_.reset(); if (inst->getEnvelopeEnabled()) envItrADPCM_ = inst->getEnvelopeSequenceIterator(); else envItrADPCM_.reset(); if (!hasArpEffADPCM_) { if (inst->getArpeggioEnabled()) arpItrADPCM_ = inst->getArpeggioSequenceIterator(); else arpItrADPCM_.reset(); } if (inst->getPitchEnabled()) ptItrADPCM_ = inst->getPitchSequenceIterator(); else ptItrADPCM_.reset(); if (inst->getPanEnabled()) panItrADPCM_ = inst->getPanSequenceIterator(); else panItrADPCM_.reset(); } void OPNAController::updateInstrumentADPCM(int instNum) { if (refInstADPCM_ && refInstADPCM_->isRegisteredWithManager() && refInstADPCM_->getNumber() == instNum) { if (!refInstADPCM_->getEnvelopeEnabled()) envItrADPCM_.reset(); if (!refInstADPCM_->getArpeggioEnabled()) arpItrADPCM_.reset(); if (!refInstADPCM_->getPitchEnabled()) ptItrADPCM_.reset(); if (!refInstADPCM_->getPanEnabled()) panItrADPCM_.reset(); } } /// NOTE: inst != nullptr void OPNAController::setInstrumentDrumkit(std::shared_ptr inst) { refInstKit_ = inst; refInstADPCM_.reset(); envItrADPCM_.reset(); arpItrADPCM_.reset(); ptItrADPCM_.reset(); } void OPNAController::updateInstrumentDrumkit(int instNum, int key) { (void)instNum; (void)key; } void OPNAController::clearSamplesADPCM() { storePointADPCM_ = 0; startAddrADPCM_ = std::numeric_limits::max(); stopAddrADPCM_ = startAddrADPCM_; } bool OPNAController::storeSampleADPCM(const std::vector& sample, size_t& startAddr, size_t& stopAddr) { // Turn on immediate-write mode to avoid suspending sample writes bool isImmediate = opna_->isImmediateWriteMode(); opna_->setImmediateWriteMode(true); opna_->setRegister(0x110, 0x80); opna_->setRegister(0x100, 0x61); opna_->setRegister(0x100, 0x60); opna_->setRegister(0x101, 0x02); size_t dramLim = (opna_->getDRAMSize() - 1) >> 5; // By 32 bytes opna_->setRegister(0x10c, dramLim & 0xff); opna_->setRegister(0x10d, (dramLim >> 8) & 0xff); bool stored = false; if (storePointADPCM_ < dramLim) { startAddr = storePointADPCM_; opna_->setRegister(0x102, startAddr & 0xff); opna_->setRegister(0x103, (startAddr >> 8) & 0xff); stopAddr = startAddr + ((sample.size() - 1) >> 5); // By 32 bytes stopAddr = std::min(stopAddr, dramLim); opna_->setRegister(0x104, stopAddr & 0xff); opna_->setRegister(0x105, (stopAddr >> 8) & 0xff); storePointADPCM_ = stopAddr + 1; size_t size = sample.size(); for (size_t i = 0; i < size; ++i) { opna_->setRegister(0x108, sample[i]); } stored = true; } opna_->setRegister(0x100, 0x00); opna_->setRegister(0x110, 0x80); opna_->setImmediateWriteMode(isImmediate); return stored; } /********** Set volume **********/ void OPNAController::setVolumeADPCM(int volume) { if (volume < bt_defs::NSTEP_ADPCM_VOLUME) { baseVolADPCM_ = volume; oneshotVolADPCM_ = UNUSED_VALUE; if (isKeyOnADPCM_) shouldWriteEnvADPCM_ = true; } } void OPNAController::setOneshotVolumeADPCM(int volume) { if (volume < bt_defs::NSTEP_ADPCM_VOLUME) { oneshotVolADPCM_ = volume; if (isKeyOnADPCM_) shouldWriteEnvADPCM_ = true; } } void OPNAController::setRealVolumeADPCM() { int volume = (oneshotVolADPCM_ == UNUSED_VALUE) ? baseVolADPCM_ : oneshotVolADPCM_; if (envItrADPCM_) { int type = envItrADPCM_->data().data; if (type >= 0) volume -= (bt_defs::NSTEP_ADPCM_VOLUME - 1 - type); } if (treItrADPCM_) volume += treItrADPCM_->data().data; volume += volSldSumADPCM_; volume = utils::clamp(volume, 0, bt_defs::NSTEP_ADPCM_VOLUME - 1); opna_->setRegister(0x10b, static_cast(volume)); shouldWriteEnvADPCM_ = false; } /********** Set effect **********/ void OPNAController::setPanADPCM(int value) { panItrADPCM_.reset(); writePanADPCM(value); } void OPNAController::setArpeggioEffectADPCM(int second, int third) { if (refInstKit_) return; if (second || third) { arpItrADPCM_ = std::make_unique(second, third); hasArpEffADPCM_ = true; } else { if (!refInstADPCM_ || !refInstADPCM_->getArpeggioEnabled()) arpItrADPCM_.reset(); else arpItrADPCM_ = refInstADPCM_->getArpeggioSequenceIterator(); hasArpEffADPCM_ = false; } } void OPNAController::setPortamentoEffectADPCM(int depth, bool isTonePortamento) { if (refInstKit_) return; prtmDepthADPCM_ = depth; hasTonePrtmADPCM_ = depth ? isTonePortamento : false; } void OPNAController::setVibratoEffectADPCM(int period, int depth) { if (refInstKit_) return; if (period && depth) vibItrADPCM_ = std::make_unique(period, depth); else vibItrADPCM_.reset(); } void OPNAController::setTremoloEffectADPCM(int period, int depth) { if (period && depth) treItrADPCM_ = std::make_unique(period, depth); else treItrADPCM_.reset(); } void OPNAController::setVolumeSlideADPCM(int depth, bool isUp) { volSldADPCM_ = isUp ? depth : -depth; } void OPNAController::setXVolumeSlideADPCM(int factor) { constexpr int COEFFICIENT = 1; constexpr int CYCLE_COUNT = 1; if (factor) xVolSldItrAdpcm_ = std::make_unique(factor * COEFFICIENT, CYCLE_COUNT); else xVolSldItrAdpcm_.reset(); } void OPNAController::setDetuneADPCM(int pitch) { if (refInstKit_) return; detuneADPCM_ = pitch; shouldSetToneADPCM_ = true; } void OPNAController::setFineDetuneADPCM(int pitch) { if (refInstKit_) return; fdetuneADPCM_ = pitch; shouldSetToneADPCM_ = true; } void OPNAController::setNoteSlideADPCM(int speed, int semitone) { if (refInstKit_) return; if (semitone) { nsItrADPCM_ = std::make_unique(speed, semitone); } else nsItrADPCM_.reset(); } void OPNAController::setTransposeEffectADPCM(int semitone) { transposeADPCM_ += (semitone * Note::SEMITONE_PITCH); shouldSetToneADPCM_ = true; } void OPNAController::setNoteCutADPCM() { keyOffADPCM(false, true); } /********** For state retrieve **********/ void OPNAController::haltSequencesADPCM() { if (treItrADPCM_) treItrADPCM_->end(); if (envItrADPCM_) envItrADPCM_->end(); if (arpItrADPCM_) arpItrADPCM_->end(); if (ptItrADPCM_) ptItrADPCM_->end(); if (vibItrADPCM_) vibItrADPCM_->end(); if (nsItrADPCM_) nsItrADPCM_->end(); if (panItrADPCM_) panItrADPCM_->end(); if (xVolSldItrAdpcm_) xVolSldItrAdpcm_->end(); } /********** Chip details **********/ bool OPNAController::isKeyOnADPCM() const { return isKeyOnADPCM_; } bool OPNAController::isTonePortamentoADPCM() const { return hasTonePrtmADPCM_; } Note OPNAController::getADPCMLatestNote() const { return echoBufADPCM_.latest(); } size_t OPNAController::getADPCMStoredSize() const { return storePointADPCM_ << 5; } /***********************************/ void OPNAController::initADPCM() { isKeyOnADPCM_ = false; hasKeyOnBeforeADPCM_ = false; refInstADPCM_.reset(); // Init envelope refInstKit_.reset(); echoBufADPCM_.clear(); neverSetBaseNoteADPCM_ = true; baseVolADPCM_ = bt_defs::NSTEP_ADPCM_VOLUME - 1; // Init volume oneshotVolADPCM_ = UNUSED_VALUE; panStateADPCM_ = PanType::CENTER << 6; shouldWriteEnvADPCM_ = false; shouldSetToneADPCM_ = false; startAddrADPCM_ = std::numeric_limits::max(); stopAddrADPCM_ = startAddrADPCM_; hasStartRequestedKit_ = false; repeatAddrADPCM_.clear(); // Init sequence shouldSkip1stTickExecADPCM_ = false; envItrADPCM_.reset(); arpItrADPCM_.reset(); ptItrADPCM_.reset(); ptSumADPCM_ = 0; panItrADPCM_.reset(); // Effect hasArpEffADPCM_ = false; prtmDepthADPCM_ = 0; hasTonePrtmADPCM_ = false; vibItrADPCM_.reset(); treItrADPCM_.reset(); volSldADPCM_ = 0; volSldSumADPCM_ = 0; xVolSldItrAdpcm_.reset(); detuneADPCM_ = 0; fdetuneADPCM_ = 0; nsItrADPCM_.reset(); nsSumADPCM_ = 0; transposeADPCM_ = 0; opna_->setRegister(0x100, 0xa1); // Stop synthesis // Limit address size_t dramLim = (opna_->getDRAMSize() - 1) >> 5; // By 32 bytes opna_->setRegister(0x10c, dramLim & 0xff); opna_->setRegister(0x10d, (dramLim >> 8) & 0xff); } void OPNAController::setMuteADPCMState(bool isMute) { isMuteADPCM_ = isMute; if (isMute) { opna_->setRegister(0x10b, 0); isKeyOnADPCM_ = false; } } bool OPNAController::isMuteADPCM() { return isMuteADPCM_; } void OPNAController::setFrontADPCMSequences() { if (isMuteADPCM_ || (!refInstADPCM_ && !refInstKit_)) return; if (treItrADPCM_) { treItrADPCM_->front(); shouldWriteEnvADPCM_ = true; } if (volSldADPCM_) { volSldSumADPCM_ += volSldADPCM_; shouldWriteEnvADPCM_ = true; } if (xVolSldItrAdpcm_) { xVolSldItrAdpcm_->next(); xslideVolumeAdpcm(); } if (envItrADPCM_) { envItrADPCM_->front(); writeEnvelopeADPCMToRegister(); } else setRealVolumeADPCM(); checkPanADPCM(1); if (arpItrADPCM_) { arpItrADPCM_->front(); checkRealToneADPCMByArpeggio(); } checkPortamentoADPCM(); if (ptItrADPCM_) { ptItrADPCM_->front(); checkRealToneADPCMByPitch(); } if (vibItrADPCM_) { vibItrADPCM_->front(); /* shouldSetToneADPCM_ = true; */ } if (nsItrADPCM_) { nsItrADPCM_->front(); if (!nsItrADPCM_->hasEnded()) { nsSumADPCM_ += nsItrADPCM_->data().data; /* shouldSetToneADPCM_ = true; */ } } writePitchADPCM(); } void OPNAController::releaseStartADPCMSequences(bool forceSilence) { if (isMuteADPCM_ || (!refInstADPCM_ && !refInstKit_)) return; if (treItrADPCM_) { treItrADPCM_->release(); shouldWriteEnvADPCM_ = true; } if (volSldADPCM_) { volSldSumADPCM_ += volSldADPCM_; shouldWriteEnvADPCM_ = true; } if (xVolSldItrAdpcm_) { xVolSldItrAdpcm_->next(); xslideVolumeAdpcm(); } bool hasSilence = false; if (envItrADPCM_) { envItrADPCM_->release(); if (forceSilence || envItrADPCM_->hasEnded()) { // Silence envItrADPCM_->end(); opna_->setRegister(0x10b, 0); shouldWriteEnvADPCM_ = false; hasSilence = true; } else writeEnvelopeADPCMToRegister(); } else { if (forceSilence) { // Silence opna_->setRegister(0x10b, 0); shouldWriteEnvADPCM_ = false; hasSilence = true; } else { // Maybe silence, need to check it before repeating process shouldWriteEnvADPCM_ = true; } } checkPanADPCM(2); if (arpItrADPCM_) { arpItrADPCM_->release(); checkRealToneADPCMByArpeggio(); } checkPortamentoADPCM(); if (ptItrADPCM_) { ptItrADPCM_->release(); checkRealToneADPCMByPitch(); } if (vibItrADPCM_) { vibItrADPCM_->release(); shouldSetToneADPCM_ = true; } if (nsItrADPCM_) { nsItrADPCM_->release(); if (!nsItrADPCM_->hasEnded()) { nsSumADPCM_ += nsItrADPCM_->data().data; shouldSetToneADPCM_ = true; } } if (shouldSetToneADPCM_) writePitchADPCM(); if (hasSilence) { // Stop synthesis opna_->setRegister(0x100, 0xa1); } else { // Play after repeat if neccessary if (refInstADPCM_) { if (refInstADPCM_->getSampleRepeatFlag() & SampleRepeatFlag::ShouldRewriteStop) { opna_->setRegister(0x100, 0xa1); size_t startAddr = refInstADPCM_->getSampleStartAddress(); SampleRepeatRange range = refInstADPCM_->getSampleRepeatRange(); triggerSamplePlayADPCM(startAddr + range.last(), refInstADPCM_->getSampleStopAddress(), false); return; } } else { int key = baseNoteADPCM_.getNoteNumber(); if (hasStartRequestedKit_ || (refInstKit_->getSampleEnabled(key) && (refInstKit_->getSampleRepeatFlag(key) & SampleRepeatFlag::ShouldRewriteStop))) { opna_->setRegister(0x100, 0xa1); size_t startAddr = refInstKit_->getSampleStartAddress(key); SampleRepeatRange range = refInstKit_->getSampleRepeatRange(key); triggerSamplePlayADPCM(startAddr + range.last(), refInstKit_->getSampleStopAddress(key), false); hasStartRequestedKit_ = false; return; } } // If there is no sample after repeat stop and it is neccessary to change envelope, stop synthesis. if (shouldWriteEnvADPCM_) { opna_->setRegister(0x100, 0xa1); } } hasStartRequestedKit_ = false; } void OPNAController::tickEventADPCM() { if (shouldSkip1stTickExecADPCM_) { shouldSkip1stTickExecADPCM_ = false; } else { if (isMuteADPCM_ || (!refInstADPCM_ && !refInstKit_)) return; if (treItrADPCM_) { treItrADPCM_->next(); shouldWriteEnvADPCM_ = true; } if (volSldADPCM_) { volSldSumADPCM_ += volSldADPCM_; shouldWriteEnvADPCM_ = true; } if (xVolSldItrAdpcm_) { xVolSldItrAdpcm_->next(); xslideVolumeAdpcm(); } if (envItrADPCM_) { envItrADPCM_->next(); writeEnvelopeADPCMToRegister(); } else if (shouldWriteEnvADPCM_) { setRealVolumeADPCM(); } checkPanADPCM(0); if (arpItrADPCM_) { arpItrADPCM_->next(); checkRealToneADPCMByArpeggio(); } checkPortamentoADPCM(); if (ptItrADPCM_) { ptItrADPCM_->next(); checkRealToneADPCMByPitch(); } if (vibItrADPCM_) { vibItrADPCM_->next(); shouldSetToneADPCM_ = true; } if (nsItrADPCM_) { nsItrADPCM_->next(); if (!nsItrADPCM_->hasEnded()) { nsSumADPCM_ += nsItrADPCM_->data().data; shouldSetToneADPCM_ = true; } } if (shouldSetToneADPCM_) writePitchADPCM(); if (hasStartRequestedKit_) { opna_->setRegister(0x101, 0x02); opna_->setRegister(0x100, 0xa1); int key = baseNoteADPCM_.getNoteNumber(); SampleRepeatFlag flag = refInstKit_->getSampleRepeatFlag(key); size_t startAddr = refInstKit_->getSampleStartAddress(key); SampleRepeatRange range = refInstKit_->getSampleRepeatRange(key); if (flag & SampleRepeatFlag::ShouldRewriteStop) { triggerSamplePlayADPCM(startAddr, startAddr + range.last(), refInstADPCM_->isSampleRepeatable()); } else { triggerSamplePlayADPCM(startAddr, refInstKit_->getSampleStopAddress(key), refInstKit_->isSampleRepeatable(key)); } if (flag & SampleRepeatFlag::ShouldRewriteStart) { repeatAddrADPCM_.push_back(startAddr + range.first()); } hasStartRequestedKit_ = false; } else if (!repeatAddrADPCM_.empty()) { // Partial repeat: rewrite start address size_t addr = repeatAddrADPCM_.front(); opna_->setRegister(0x102, addr & 0xff); opna_->setRegister(0x103, (addr >> 8) & 0xff); startAddrADPCM_ = addr; repeatAddrADPCM_.pop_back(); } } } void OPNAController::xslideVolumeAdpcm() { bool isChanged; auto slide = [v = xVolSldItrAdpcm_->data().data](int& target) { return std::exchange(target, utils::clamp(target + v, 0, bt_defs::NSTEP_ADPCM_VOLUME - 1)); }; int prevBase = slide(baseVolADPCM_); if (oneshotVolADPCM_ == UNUSED_VALUE) { isChanged = (baseVolADPCM_ != prevBase); } else { int prevOneShot = slide(oneshotVolADPCM_); isChanged = (oneshotVolADPCM_ != prevOneShot); } shouldWriteEnvADPCM_ |= isChanged; } void OPNAController::writeEnvelopeADPCMToRegister() { if (!envItrADPCM_->hasEnded() || shouldWriteEnvADPCM_) { setRealVolumeADPCM(); } } void OPNAController::checkPanADPCM(int type) { if (panItrADPCM_) { switch (type) { case 0: panItrADPCM_->next(); break; case 1: panItrADPCM_->front(); break; case 2: panItrADPCM_->release(); break; default: throw std::out_of_range("The range of type is 0-2."); } if (!panItrADPCM_->hasEnded()) writePanADPCM(panItrADPCM_->data().data); } else if (refInstKit_ && type == 1) { int key = calculateCurrentKeyOfDrumkit(baseNoteADPCM_.getNoteNumber(), transposeADPCM_); if (refInstKit_->getSampleEnabled(key)) { writePanADPCM(refInstKit_->getPan(key)); } } } void OPNAController::writePanADPCM(int pos) { uint8_t v = static_cast(pos << 6); if (v != panStateADPCM_) { panStateADPCM_ = v; opna_->setRegister(0x101, panStateADPCM_ | 0x02); } } void OPNAController::writePitchADPCM() { if (neverSetBaseNoteADPCM_) return; if (refInstADPCM_) { int p = (baseNoteADPCM_ + (ptSumADPCM_ + (vibItrADPCM_ ? vibItrADPCM_->data().data : 0) + detuneADPCM_ + nsSumADPCM_ + transposeADPCM_)).getAbsolutePicth(); int diff = p - Note::SEMITONE_PITCH * refInstADPCM_->getSampleRootKeyNumber(); writePitchADPCMToRegister(diff, refInstADPCM_->getSampleRootDeltaN()); } else if (refInstKit_) { int key = calculateCurrentKeyOfDrumkit(baseNoteADPCM_.getNoteNumber(), transposeADPCM_); if (refInstKit_->getSampleEnabled(key)) { int diff = Note::SEMITONE_PITCH * refInstKit_->getPitch(key); writePitchADPCMToRegister(diff, refInstKit_->getSampleRootDeltaN(key)); hasStartRequestedKit_ = true; } } shouldSetToneADPCM_ = false; } void OPNAController::writePitchADPCMToRegister(int pitchDiff, int rtDeltaN) { int deltan = static_cast(std::round(rtDeltaN * std::pow(2., pitchDiff / 384.))) + fdetuneADPCM_; opna_->setRegister(0x109, deltan & 0xff); opna_->setRegister(0x10a, (deltan >> 8) & 0xff); } void OPNAController::triggerSamplePlayADPCM(size_t startAddress, size_t stopAddress, bool shouldRepeat) { // Reset synthesis opna_->setRegister(0x100, 0x21); if (startAddress != startAddrADPCM_) { opna_->setRegister(0x102, startAddress & 0xff); opna_->setRegister(0x103, (startAddress >> 8) & 0xff); startAddrADPCM_ = startAddress; } if (stopAddress != stopAddrADPCM_) { opna_->setRegister(0x104, stopAddress & 0xff); opna_->setRegister(0x105, (stopAddress >> 8) & 0xff); stopAddrADPCM_ = stopAddress; } uint8_t repeatFlag = shouldRepeat ? 0x10 : 0; opna_->setRegister(0x100, 0xa0 | repeatFlag); opna_->setRegister(0x101, panStateADPCM_ | 0x02); } BambooTracker-0.6.5/BambooTracker/opna_controller.hpp000066400000000000000000000434501476276175200227100ustar00rootroot00000000000000/* * Copyright (C) 2018-2023 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include #include "song.hpp" #include "instrument.hpp" #include "effect_iterator.hpp" #include "sample_repeat.hpp" #include "note.hpp" #include "echo_buffer.hpp" #include "chip/opna.hpp" #include "enum_hash.hpp" #include "bamboo_tracker_defs.hpp" class OPNAController { public: OPNAController(chip::OpnaEmulator emu, int clock, int rate, int duration, chip::ResamplerType resampler); // Reset and initialize void reset(); // Forward instrument sequence void tickEvent(SoundSource src, int ch); // Direct register set void sendRegister(int address, int value); // DRAM size_t getDRAMSize() const; // Update register states after tick process void updateRegisterStates(); // Real chip interface void connectToRealChip(RealChipInterfaceType type, RealChipInterfaceGeneratorFunc* f); RealChipInterfaceType getRealChipInterfaceType() const; bool hasConnectedToRealChip() const; // Stream samples /** * @brief getStreamSamples * @param container buffer where generated samples are stored. * @param nSamples number of generated samples * @return true if sample generatrion is success, otherwise false. */ bool getStreamSamples(int16_t* container, size_t nSamples); void getOutputHistory(int16_t* history); // Chip mode void setMode(SongType mode); SongType getMode() const noexcept; void setImmediateWriteMode(bool enabled) noexcept; // Mute void setMuteState(SoundSource src, int chInSrc, bool isMute); bool isMute(SoundSource src, int chInSrc); // Stream details int getRate() const; void setRate(int rate); int getDuration() const; void setDuration(int duration); void setResampler(chip::ResamplerType type); void setMasterVolume(int percentage); // Export void setExportContainer(std::shared_ptr cntr = nullptr); private: std::unique_ptr opna_; SongType mode_; struct RegisterWrite { uint32_t address; uint8_t data; }; std::deque registerDirectSetBuf_; void resetState(); std::unique_ptr outputHistory_; size_t outputHistoryIndex_; std::unique_ptr outputHistoryReady_; std::mutex outputHistoryReadyMutex_; void fillOutputHistory(const int16_t* outputs, size_t nSamples); using ArpeggioIterInterface = std::unique_ptr>; void checkRealToneByArpeggio(const ArpeggioIterInterface& arpItr, const EchoBuffer& echoBuf, Note& baseNote, bool& shouldSetTone); void checkPortamento(const ArpeggioIterInterface& arpItr, int prtm, bool hasKeyOnBefore, bool isTonePrtm, EchoBuffer& echoBuf, Note& baseNote, bool& shouldSetTone); void checkRealToneByPitch(const std::unique_ptr::Iterator>& ptItr, int& sumPitch, bool& shouldSetTone); /*----- FM -----*/ public: // Key on-off void keyOnFM(int ch, const Note& note, bool isJam = false); void keyOnFM(int ch, int echoBuf); void keyOffFM(int ch, bool isJam = false); void retriggerKeyOnFM(int ch, int volDiff); // Set instrument void setInstrumentFM(int ch, std::shared_ptr inst); void updateInstrumentFM(int instNum); void updateInstrumentFMEnvelopeParameter(int envNum, FMEnvelopeParameter param); void setInstrumentFMOperatorEnabled(int envNum, int opNum); void updateInstrumentFMLFOParameter(int lfoNum, FMLFOParameter param); void resetFMChannelEnvelope(int ch); void restoreFMEnvelopeFromReset(int ch); // Set volume void setVolumeFM(int ch, int volume); void setOneshotVolumeFM(int ch, int volume); void setMasterVolumeFM(double dB); double getMasterVolumeFM() const; // Set effect void setPanFM(int ch, int value); void setArpeggioEffectFM(int ch, int second, int third); void setPortamentoEffectFM(int ch, int depth, bool isTonePortamento = false); void setVibratoEffectFM(int ch, int period, int depth); void setTremoloEffectFM(int ch, int period, int depth); void setVolumeSlideFM(int ch, int depth, bool isUp); void setXVolumeSlideFM(int ch, int factor); void setDetuneFM(int ch, int pitch); void setFineDetuneFM(int ch, int pitch); void setNoteSlideFM(int ch, int speed, int semitone); void setTransposeEffectFM(int ch, int semitone); void setFBControlFM(int ch, int value); void setTLControlFM(int ch, int op, int value); void setMLControlFM(int ch, int op, int value); void setARControlFM(int ch, int op, int value); void setDRControlFM(int ch, int op, int value); void setRRControlFM(int ch, int op, int value); void setBrightnessFM(int ch, int value); // For state retrieve void haltSequencesFM(int ch); // Chip details bool isKeyOnFM(int ch) const; bool isTonePortamentoFM(int ch) const; bool enableFMEnvelopeReset(int ch) const; Note getFMLatestNote(int ch) const; private: // Seperate FM 3ch in FM3chEx mode struct FMChannel { size_t ch, inCh; bool isKeyOn, hasKeyOnBefore; EchoBuffer echoBuf; bool neverSetBaseNote; Note baseNote; int baseVol, oneshotVol; bool isMute; bool isEnabledEnvReset, hasResetEnv; bool hasPreSetTickEvent; bool shouldSetTone; ArpeggioIterInterface arpItr; PitchIter ptItr; int ptSum; bool isArpEff; int prtmDepth; bool isTonePrtm; std::unique_ptr vibItr; std::unique_ptr treItr; int volSld, volSldSum; std::unique_ptr xVolSldItr; int xvolSldSum; int detune, fdetune; std::unique_ptr nsItr; int nsSum; int transpose; FMOperatorType opType; }; FMChannel fm_[9]; // Share FM 3ch in FM3chEx mode std::shared_ptr refInstFM_[6]; std::unique_ptr envFM_[6]; uint8_t fmOpEnables_[6]; uint8_t panStateFM_[6]; PanIter panItrFM_[6]; int lfoFreq_; int lfoStartCntFM_[6]; std::unordered_map opSeqIrtFM_[6]; bool isFBCtrlFM_[6], isTLCtrlFM_[6][4], isMLCtrlFM_[6][4], isARCtrlFM_[6][4]; bool isDRCtrlFM_[6][4], isRRCtrlFM_[6][4]; bool isBrightFM_[6][4]; void initFM(); void setMuteFMState(int ch, bool isMuteFM); bool isMuteFM(int ch); void updateFMVolume(FMChannel& fm); void writeFMEnvelopeToRegistersFromInstrument(size_t inch); void writeFMEnveropeParameterToRegister(size_t inch, FMEnvelopeParameter param, int value); void writeFMLFOAllRegisters(size_t inch); void writeFMLFORegister(size_t inch, FMLFOParameter param); void checkLFOUsedByInstrument(); void setFrontFMSequences(FMChannel& fm); void releaseStartFMSequences(FMChannel& fm); void tickEventFM(FMChannel& fm); void checkOperatorSequenceFM(FMChannel& fm, int type); void checkVolumeEffectFM(FMChannel& fm); void checkPanFM(FMChannel& fm, int type); void writePanFM(FMChannel& fm, int pos); void checkRealToneFMByArpeggio(FMChannel& fm); void checkPortamentoFM(FMChannel& fm); void checkRealToneFMByPitch(FMChannel& fm); void writePitchFM(FMChannel& fm); void setInstrumentFMProperties(FMChannel& fm); uint8_t getFM3SlotValidStatus() const; uint8_t calculateTL(int ch, uint8_t data) const; /*----- SSG -----*/ public: // Key on-off void keyOnSSG(int ch, const Note& note, bool isJam = false); void keyOnSSG(int ch, int echoBuf); void keyOffSSG(int ch, bool isJam = false); void retriggerKeyOnSSG(int ch, int volDiff); // Set instrument void setInstrumentSSG(int ch, std::shared_ptr inst); void updateInstrumentSSG(int instNum); // Set volume void setVolumeSSG(int ch, int volume); void setOneshotVolumeSSG(int ch, int volume); void setMasterVolumeSSG(double dB); double getMasterVolumeSSG() const; // Set effect void setArpeggioEffectSSG(int ch, int second, int third); void setPortamentoEffectSSG(int ch, int depth, bool isTonePortamento = false); void setVibratoEffectSSG(int ch, int period, int depth); void setTremoloEffectSSG(int ch, int period, int depth); void setVolumeSlideSSG(int ch, int depth, bool isUp); void setXVolumeSlideSSG(int ch, int factor); void setDetuneSSG(int ch, int pitch); void setFineDetuneSSG(int ch, int pitch); void setNoteSlideSSG(int ch, int speed, int semitone); void setTransposeEffectSSG(int ch, int semitone); void setToneNoiseMixSSG(int ch, int value); void setNoisePitchSSG(int ch, int pitch); void setHardEnvelopePeriod(int ch, bool high, int period); void setAutoEnvelopeSSG(int ch, int shift, int shape); void setNoteCutSSG(int ch); void haltSequencesSSG(int ch); // Chip details bool isKeyOnSSG(int ch) const; bool isTonePortamentoSSG(int ch) const; Note getSSGLatestNote(int ch) const; private: struct SSGChannel { size_t ch; std::shared_ptr refInst; bool isKeyOn, hasKeyOnBefore, isInKeyOnProcess_; EchoBuffer echoBuf; bool neverSetBaseNote; Note baseNote; int baseVol, oneshotVol; bool isMute; bool shouldSkip1stTickExec; bool shouldSetEnv; bool shouldSetSqMaskFreq; bool shouldSetHardEnvFreq; bool shouldUpdateMixState; bool shouldSetTone; SSGWaveformIter wfItr; SSGWaveformUnit wfChState; SSGEnvelopeIter envItr; SSGEnvelopeUnit envState; bool isHardEnv; SSGToneNoiseIter tnItr; bool hasRequestedTnEffSet; ArpeggioIterInterface arpItr; PitchIter ptItr; int ptSum; bool isArpEff; int prtmDepth; bool isTonePrtm; std::unique_ptr vibItr; std::unique_ptr treItr; int volSld, volSldSum; std::unique_ptr xVolSldItr; int detune, fdetune; std::unique_ptr nsItr; int nsSum; int transpose; } ssg_[3]; /// Flag "on" is key "off" uint8_t mixerSSG_; uint8_t noisePeriodSSG_; int hardEnvPeriodHighSSG_, hardEnvPeriodLowSSG_; void initSSG(); void keyOffSSG(int ch, bool isJam, bool forceSilence); void setMuteSSGState(int ch, bool isMuteFM); bool isMuteSSG(int ch); void setFrontSSGSequences(SSGChannel& ssg); void releaseStartSSGSequences(SSGChannel& ssg, bool forceSilince); void tickEventSSG(SSGChannel& ssg); void writeWaveformSSGToRegister(SSGChannel& ssg); void writeSquareWaveform(SSGChannel& ssg); void xslideVolumeSsg(SSGChannel& ssg); void writeEnvelopeSSGToRegister(SSGChannel& ssg); void setRealVolumeSSG(SSGChannel& ssg); void writeMixerSSGToRegisterByEffect(SSGChannel& ssg); void writeMixerSSGToRegisterBySequence(SSGChannel& ssg); void writeMixerSSGToRegisterByNoReference(SSGChannel& ssg); void checkRealToneSSGByArpeggio(SSGChannel& ssg); void checkPortamentoSSG(SSGChannel& ssg); void checkRealToneSSGByPitch(SSGChannel& ssg); void writePitchSSG(SSGChannel& ssg); void writeAutoEnvelopePitchSSG(SSGChannel& ssg, double tonePitch); void writeSquareMaskPitchSSG(SSGChannel& ssg, double tonePitch, bool isTriangle); /*----- Rhythm -----*/ public: // Key on/off void setKeyOnFlagRhythm(int ch); void setKeyOffFlagRhythm(int ch); void retriggerKeyOnFlagRhythm(int ch, int volDiff); void updateKeyOnOffStatusRhythm(bool isJam = false); // Set volume void setVolumeRhythm(int ch, int volume); void setOneshotVolumeRhythm(int ch, int volume); void setMasterVolumeRhythm(int volume); // Set effect void setPanRhythm(int ch, int value); private: struct RhythmChannel { int baseVol, oneshotVol; /// bit0: right on/off /// bit1: left on/off uint8_t panState; bool isMute; } rhythm_[6]; uint8_t keyOnRequestFlagsRhythm_, keyOffRequestFlagsRhythm_; int masterVolRhythm_; void initRhythm(); void setMuteRhythmState(int ch, bool isMute); bool isMuteRhythm(int ch); /*----- ADPCM/Drumkit -----*/ public: // Key on-off void keyOnADPCM(const Note& note, bool isJam = false); void keyOnADPCM(int echoBuf); void keyOffADPCM(bool isJam = false); void retriggerKeyOnADPCM(int volDiff); // Set instrument void setInstrumentADPCM(std::shared_ptr inst); void updateInstrumentADPCM(int instNum); void setInstrumentDrumkit(std::shared_ptr inst); void updateInstrumentDrumkit(int instNum, int key); void clearSamplesADPCM(); /// [Return] true if sample assignment is success bool storeSampleADPCM(const std::vector& sample, size_t& startAddr, size_t& stopAddr); // Set volume void setVolumeADPCM(int volume); void setOneshotVolumeADPCM(int volume); // Set effect void setPanADPCM(int value); void setArpeggioEffectADPCM(int second, int third); void setPortamentoEffectADPCM(int depth, bool isTonePortamento = false); void setVibratoEffectADPCM(int period, int depth); void setTremoloEffectADPCM(int period, int depth); void setVolumeSlideADPCM(int depth, bool isUp); void setXVolumeSlideADPCM(int factor); void setDetuneADPCM(int pitch); void setFineDetuneADPCM(int pitch); void setNoteSlideADPCM(int speed, int semitone); void setTransposeEffectADPCM(int semitone); void setNoteCutADPCM(); // For state retrieve void haltSequencesADPCM(); // Chip details bool isKeyOnADPCM() const; bool isTonePortamentoADPCM() const; Note getADPCMLatestNote() const; size_t getADPCMStoredSize() const; private: std::shared_ptr refInstADPCM_; std::shared_ptr refInstKit_; bool isKeyOnADPCM_, hasKeyOnBeforeADPCM_; EchoBuffer echoBufADPCM_; bool neverSetBaseNoteADPCM_; Note baseNoteADPCM_; int baseVolADPCM_, oneshotVolADPCM_; uint8_t panStateADPCM_; PanIter panItrADPCM_; bool isMuteADPCM_; bool shouldSkip1stTickExecADPCM_; // Use to execute key on/off process in jamming bool shouldWriteEnvADPCM_; bool shouldSetToneADPCM_; size_t startAddrADPCM_, stopAddrADPCM_; // By 32 bytes size_t storePointADPCM_; // Move by 32 bytes ADPCMEnvelopeIter envItrADPCM_; ArpeggioIterInterface arpItrADPCM_; PitchIter ptItrADPCM_; int ptSumADPCM_; bool hasArpEffADPCM_; int prtmDepthADPCM_; bool hasTonePrtmADPCM_; std::unique_ptr vibItrADPCM_; std::unique_ptr treItrADPCM_; int volSldADPCM_, volSldSumADPCM_; std::unique_ptr xVolSldItrAdpcm_; int detuneADPCM_, fdetuneADPCM_; std::unique_ptr nsItrADPCM_; int nsSumADPCM_; int transposeADPCM_; bool hasStartRequestedKit_; std::vector repeatAddrADPCM_; void initADPCM(); void keyOffADPCM(bool isJam, bool forceSilence); void setMuteADPCMState(bool isMuteFM); bool isMuteADPCM(); void setFrontADPCMSequences(); void releaseStartADPCMSequences(bool forceSilence); void tickEventADPCM(); void xslideVolumeAdpcm(); void writeEnvelopeADPCMToRegister(); void checkPanADPCM(int type); void writePanADPCM(int pos); void checkRealToneADPCMByArpeggio(); void checkPortamentoADPCM(); void checkRealToneADPCMByPitch(); void writePitchADPCM(); void writePitchADPCMToRegister(int pitchDiff, int rtDeltaN); void setRealVolumeADPCM(); void triggerSamplePlayADPCM(size_t startAddress, size_t stopAddress, bool shouldRepeat); }; //----------------------------------------------------------------------------- inline SongType OPNAController::getMode() const noexcept { return mode_; } /*----- FM -----*/ inline void OPNAController::checkRealToneFMByArpeggio(FMChannel& fm) { checkRealToneByArpeggio(fm.arpItr, fm.echoBuf, fm.baseNote, fm.shouldSetTone); } inline void OPNAController::checkPortamentoFM(FMChannel& fm) { checkPortamento(fm.arpItr, fm.prtmDepth, fm.hasKeyOnBefore, fm.isTonePrtm, fm.echoBuf, fm.baseNote, fm.shouldSetTone); } inline void OPNAController::checkRealToneFMByPitch(FMChannel& fm) { checkRealToneByPitch(fm.ptItr, fm.ptSum, fm.shouldSetTone); } inline uint8_t OPNAController::getFM3SlotValidStatus() const { return fmOpEnables_[2] & (static_cast(fm_[2].isKeyOn) | (static_cast(fm_[6].isKeyOn) << 1) | (static_cast(fm_[7].isKeyOn) << 2) | (static_cast(fm_[8].isKeyOn) << 3)); } /*----- SSG -----*/ inline void OPNAController::checkRealToneSSGByArpeggio(OPNAController::SSGChannel& ssg) { checkRealToneByArpeggio(ssg.arpItr, ssg.echoBuf, ssg.baseNote, ssg.shouldSetTone); } inline void OPNAController::checkPortamentoSSG(OPNAController::SSGChannel& ssg) { checkPortamento(ssg.arpItr, ssg.prtmDepth, ssg.hasKeyOnBefore, ssg.isTonePrtm, ssg.echoBuf, ssg.baseNote, ssg.shouldSetTone); } inline void OPNAController::checkRealToneSSGByPitch(OPNAController::SSGChannel& ssg) { checkRealToneByPitch(ssg.ptItr, ssg.ptSum, ssg.shouldSetTone); } inline void OPNAController::keyOffSSG(int ch, bool isJam) { keyOffSSG(ch, isJam, false); } /*----- ADPCM/Drumkit -----*/ inline void OPNAController::checkRealToneADPCMByArpeggio() { checkRealToneByArpeggio(arpItrADPCM_, echoBufADPCM_, baseNoteADPCM_, shouldSetToneADPCM_); } inline void OPNAController::checkPortamentoADPCM() { checkPortamento(arpItrADPCM_, prtmDepthADPCM_, hasKeyOnBeforeADPCM_, hasTonePrtmADPCM_, echoBufADPCM_, baseNoteADPCM_, shouldSetToneADPCM_); } inline void OPNAController::checkRealToneADPCMByPitch() { checkRealToneByPitch(ptItrADPCM_, ptSumADPCM_, shouldSetToneADPCM_); } inline void OPNAController::keyOffADPCM(bool isJam) { keyOffADPCM(isJam, false); } BambooTracker-0.6.5/BambooTracker/playback.cpp000066400000000000000000002106141476276175200212670ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2019 Rerrah * SPDX-License-Identifier: MIT */ #include "playback.hpp" #include #include "opna_controller.hpp" #include "instruments_manager.hpp" #include "tick_counter.hpp" #include "note.hpp" #include "utils.hpp" EffectMemory::EffectMemory() { mem_.reserve(Step::N_EFFECT); } namespace { constexpr auto isEqualEffectType(EffectType type) { return [type](const Effect& effect) { return effect.type == type; }; } } void EffectMemory::enqueue(const Effect& eff) { auto itr = utils::findIf(mem_, isEqualEffectType(eff.type)); if (itr != mem_.end()) mem_.erase(itr); mem_.push_back(eff); switch (eff.type) { case EffectType::HardEnvHighPeriod: case EffectType::HardEnvLowPeriod: { // Call auto envelope effect after setting period to reset envelope phase. auto endPrevEff = mem_.cend() - 1; auto autoEnvItr = std::find_if(mem_.cbegin(), endPrevEff, isEqualEffectType(EffectType::AutoEnvelope)); if (autoEnvItr == endPrevEff) { return; } auto autoEnv = std::move(*autoEnvItr); mem_.erase(autoEnvItr); mem_.push_back(std::move(autoEnv)); break; } default: break; } } void EffectMemory::clear() { mem_.clear(); } //----------------------------------------------- namespace { namespace PlayStateFlag { enum : uint8_t { Clear = 0, // Read state Playing = 1 << 0, ReadFirstStep = 1 << 1, // Play type LoopPattern = 1 << 2, PlayStep = 1 << 3 }; } } PlaybackManager::PlaybackManager(std::shared_ptr opnaCtrl, std::weak_ptr instMan, std::weak_ptr tickCounter, std::weak_ptr mod, bool isRetrieveChannel) : opnaCtrl_(opnaCtrl), instMan_(instMan), tickCounter_(tickCounter), mod_(mod), curSongNum_(0), playingPos_(Position::INVALID, Position::INVALID), nextReadPos_(Position::INVALID, Position::INVALID), isFindNextStep_(false), isRetrieveChannel_(isRetrieveChannel) { songStyle_ = mod.lock()->getSong(curSongNum_).getStyle(); clearEffectMaps(); clearDelayWithinStepCounts(); clearDelayBeyondStepCounts(); } void PlaybackManager::setSong(std::weak_ptr mod, int songNum) { mod_ = mod; curSongNum_ = songNum; songStyle_ = mod_.lock()->getSong(curSongNum_).getStyle(); /* opna mode is changed in BambooTracker class */ size_t fmch = Song::getFMChannelCount(songStyle_.type); effOnKeyOnMem_[SoundSource::FM] = std::vector(fmch); effOnStepBeginMem_[SoundSource::FM] = std::vector(fmch); directRegisterSets_[SoundSource::FM] = DirectRegisterSetSource(fmch); ntDlyCntFM_ = std::vector(fmch); ntReleaseDlyCntFM_ = std::vector(fmch); volDlyCntFM_ = std::vector(fmch); volDlyValueFM_ = std::vector(fmch, -1); tposeDlyCntFM_ = std::vector(fmch); tposeDlyValueFM_ = std::vector(fmch); ntCutDlyCntFM_ = std::vector(fmch); rtrgCntFM_ = std::vector(fmch); rtrgCntValueFM_ = std::vector(fmch); rtrgVolValueFM_ = std::vector(fmch); effOnKeyOnMem_[SoundSource::SSG] = std::vector(3); effOnStepBeginMem_[SoundSource::SSG] = std::vector(3); directRegisterSets_[SoundSource::SSG] = DirectRegisterSetSource(3); ntDlyCntSSG_ = std::vector(3); ntReleaseDlyCntSSG_ = std::vector(3); volDlyCntSSG_ = std::vector(3); volDlyValueSSG_ = std::vector(3, -1); tposeDlyCntSSG_ = std::vector(3); tposeDlyValueSSG_ = std::vector(3); ntCutDlyCntSSG_ = std::vector(3); rtrgCntSSG_ = std::vector(3); rtrgCntValueSSG_ = std::vector(3); rtrgVolValueSSG_ = std::vector(3); effOnKeyOnMem_[SoundSource::RHYTHM] = std::vector(6); effOnStepBeginMem_[SoundSource::RHYTHM] = std::vector(6); directRegisterSets_[SoundSource::RHYTHM] = DirectRegisterSetSource(6); ntDlyCntRhythm_ = std::vector(6); ntReleaseDlyCntRhythm_ = std::vector(6); volDlyCntRhythm_ = std::vector(6); volDlyValueRhythm_ = std::vector(6, -1); ntCutDlyCntRhythm_ = std::vector(6); rtrgCntRhythm_ = std::vector(6); rtrgCntValueRhythm_ = std::vector(6); rtrgVolValueRhythm_ = std::vector(6); effOnKeyOnMem_[SoundSource::ADPCM] = std::vector(1); effOnStepBeginMem_[SoundSource::ADPCM] = std::vector(1); directRegisterSets_[SoundSource::ADPCM] = DirectRegisterSetSource(1); ntDlyCntADPCM_ = 0; ntReleaseDlyCntADPCM_ = 0; volDlyCntADPCM_ = 0; volDlyValueADPCM_ = -1; ntCutDlyCntADPCM_ = 0; rtrgCntADPCM_ = 0; rtrgCntValueADPCM_ = 0; rtrgVolValueADPCM_ = 0; } /********** Play song **********/ void PlaybackManager::startPlaySong(int order) { std::lock_guard lock(mutex_); startPlay(); playStateFlags_ = PlayStateFlag::Playing; playingPos_.set(order, 0); findNextStep(); if (isRetrieveChannel_) retrieveChannelStates(); } void PlaybackManager::startPlayFromStart() { std::lock_guard lock(mutex_); startPlay(); playStateFlags_ = PlayStateFlag::Playing; playingPos_.set(0, 0); findNextStep(); } void PlaybackManager::startPlayPattern(int order) { std::lock_guard lock(mutex_); startPlay(); playStateFlags_ = PlayStateFlag::Playing | PlayStateFlag::LoopPattern; playingPos_.set(order, 0); findNextStep(); if (isRetrieveChannel_) retrieveChannelStates(); } void PlaybackManager::startPlayFromPosition(int order, int step) { std::lock_guard lock(mutex_); startPlay(); playStateFlags_ = PlayStateFlag::Playing; playingPos_.set(order, step); findNextStep(); if (isRetrieveChannel_) retrieveChannelStates(); } void PlaybackManager::playStep(int order, int step) { std::lock_guard lock(mutex_); bool isPlaying = isPlayingStep(); if (!isPlaying) { opnaCtrl_->reset(); Song& song = mod_.lock()->getSong(curSongNum_); tickCounter_.lock()->setTempo(song.getTempo()); tickCounter_.lock()->setSpeed(song.getSpeed()); tickCounter_.lock()->setGroove(mod_.lock()->getGroove(song.getGroove())); tickCounter_.lock()->setGrooveState(song.isUsedTempo() ? GrooveState::Invalid : GrooveState::ValidByGlobal); } tickCounter_.lock()->resetCount(); tickCounter_.lock()->setPlayState(true); clearEffectMaps(); clearDelayWithinStepCounts(); clearDelayBeyondStepCounts(); playStateFlags_ = PlayStateFlag::PlayStep; playingPos_.set(order, step); if (!isPlaying && isRetrieveChannel_) retrieveChannelStates(); } void PlaybackManager::startPlay() { opnaCtrl_->reset(); Song& song = mod_.lock()->getSong(curSongNum_); tickCounter_.lock()->setTempo(song.getTempo()); tickCounter_.lock()->setSpeed(song.getSpeed()); tickCounter_.lock()->setGroove(mod_.lock()->getGroove(song.getGroove())); tickCounter_.lock()->setGrooveState(song.isUsedTempo() ? GrooveState::Invalid : GrooveState::ValidByGlobal); tickCounter_.lock()->resetCount(); tickCounter_.lock()->setPlayState(true); clearEffectMaps(); clearDelayWithinStepCounts(); clearDelayBeyondStepCounts(); } void PlaybackManager::stopPlaySong() { std::lock_guard lock(mutex_); stopPlay(); } void PlaybackManager::stopPlay() { // No mutex to call from PlaybackManager::streamCountUp opnaCtrl_->reset(); tickCounter_.lock()->setPlayState(false); playStateFlags_ = PlayStateFlag::Clear; playingPos_.invalidate(); } bool PlaybackManager::isPlaySong() const noexcept { return playStateFlags_ & PlayStateFlag::Playing; } bool PlaybackManager::isPlayingStep() const noexcept { return playStateFlags_ & PlayStateFlag::PlayStep; } int PlaybackManager::getPlayingOrderNumber() const noexcept { return playingPos_.order; } int PlaybackManager::getPlayingStepNumber() const noexcept { return playingPos_.step; } /********** Stream events **********/ int PlaybackManager::streamCountUp() { std::lock_guard lock(mutex_); int state = tickCounter_.lock()->countUp(); if (state > 0) { // Tick process in playback checkValidPosition(); tickProcess(state); } else if (!state) { // Step process in playback checkValidPosition(); if (stepDown()) { stepProcess(); if (!isFindNextStep_) findNextStep(); } else if (!isPlayingStep()) { stopPlay(); } } else { // Stop playback for (auto& attrib : songStyle_.trackAttribs) { opnaCtrl_->tickEvent(attrib.source, attrib.channelInSource); } } return state; } bool PlaybackManager::stepDown() { if (playStateFlags_ & PlayStateFlag::ReadFirstStep) { // Foward current step if (isPlayingStep()) { return false; } else { if (!nextReadPos_.isValid()) { isFindNextStep_ = false; return false; } else { playingPos_ = nextReadPos_; } } } else { // First read playStateFlags_ |= PlayStateFlag::ReadFirstStep; } return true; } void PlaybackManager::findNextStep() { // Init nextReadPos_ = playingPos_; // Search int ptnSize = static_cast(getPatternSizeFromOrderNumber(curSongNum_, nextReadPos_.order)); if (!ptnSize || nextReadPos_.step >= ptnSize - 1) { if (!(playStateFlags_ & PlayStateFlag::LoopPattern)) { // Loop pattern if (nextReadPos_.order >= static_cast(getOrderSize(curSongNum_)) - 1) { nextReadPos_.order = 0; } else { ++nextReadPos_.order; } } nextReadPos_.step = 0; } else { ++nextReadPos_.step; } isFindNextStep_ = true; } void PlaybackManager::checkValidPosition() { auto& song = mod_.lock()->getSong(curSongNum_); int orderSize = static_cast(song.getOrderSize()); if (playingPos_.order >= orderSize) { playingPos_.set(0, 0); nextReadPos_ = playingPos_; } else if (playingPos_.step >= static_cast(song.getPatternSizeFromOrderNumber(playingPos_.order))) { if (playingPos_.order == orderSize - 1) { playingPos_.set(0, 0); nextReadPos_ = playingPos_; } else { ++playingPos_.order; playingPos_.step = 0; nextReadPos_ = playingPos_; } } } /// Register update order: volume -> instrument -> effect -> key on void PlaybackManager::stepProcess() { clearDelayWithinStepCounts(); updateDelayEventCounts(); auto& song = mod_.lock()->getSong(curSongNum_); // Store effects from the step to map for (auto& attrib : songStyle_.trackAttribs) { auto& step = song.getTrack(attrib.number) .getPatternFromOrderNumber(playingPos_.order).getStep(playingPos_.step); size_t uch = static_cast(attrib.channelInSource); effOnKeyOnMem_[attrib.source].at(uch).clear(); directRegisterSets_[attrib.source].at(uch).clear(); using storeFunc = void (PlaybackManager::*)(int, const Effect&); static const std::unordered_map storeEffectToMap = { { SoundSource::FM, &PlaybackManager::storeEffectToMapFM }, { SoundSource::SSG, &PlaybackManager::storeEffectToMapSSG }, { SoundSource::RHYTHM, &PlaybackManager::storeEffectToMapRhythm }, { SoundSource::ADPCM, &PlaybackManager::storeEffectToMapADPCM } }; for (int i = 0; i < Step::N_EFFECT; ++i) { Effect&& eff = effect_utils::validateEffect(attrib.source, step.getEffect(i)); (this->*storeEffectToMap.at(attrib.source))(attrib.channelInSource, eff); } } // Execute step events bool isNextSet = executeStoredEffectsGlobal(); const int countsInStep = tickCounter_.lock()->getCountsInCurrentStep(); for (auto& attrib : songStyle_.trackAttribs) { // Check whether it has been set note delay effect size_t uch = static_cast(attrib.channelInSource); bool hasSetNoteDelay = false; auto& mem = effOnStepBeginMem_[attrib.source].at(uch); for (auto itr = mem.begin(); itr != mem.end(); ) { if (itr->type == EffectType::NoteDelay) { if (itr->value < countsInStep) { hasSetNoteDelay = true; } else { itr = mem.erase(itr); continue; } } ++itr; } // auto& step = song.getTrack(attrib.number) .getPatternFromOrderNumber(playingPos_.order).getStep(playingPos_.step); switch (attrib.source) { case SoundSource::FM: if (hasSetNoteDelay) { // Set effect executeStoredEffectsFM(attrib.channelInSource); checkFMNoteDelayAndEnvelopeReset(step, attrib.channelInSource); opnaCtrl_->tickEvent(SoundSource::FM, attrib.channelInSource); } else { executeFMStepEvents(step, attrib.channelInSource); } break; case SoundSource::SSG: if (hasSetNoteDelay) { // Set effect executeStoredEffectsSSG(attrib.channelInSource); opnaCtrl_->tickEvent(SoundSource::SSG, attrib.channelInSource); } else { executeSSGStepEvents(step, attrib.channelInSource); } break; case SoundSource::RHYTHM: if (hasSetNoteDelay) { // Set effect executeStoredEffectsRhythm(attrib.channelInSource); opnaCtrl_->tickEvent(SoundSource::RHYTHM, attrib.channelInSource); } else { executeRhythmStepEvents(step, attrib.channelInSource); } break; case SoundSource::ADPCM: if (hasSetNoteDelay) { // Set effect executeStoredEffectsADPCM(); opnaCtrl_->tickEvent(SoundSource::ADPCM, attrib.channelInSource); } else { executeADPCMStepEvents(step); } break; } } opnaCtrl_->updateRegisterStates(); // Update for other changes isFindNextStep_ = isNextSet; } void PlaybackManager::executeFMStepEvents(const Step& step, int ch, bool calledByNoteDelay) { if (!calledByNoteDelay && !step.isEmptyNote()) clearFMDelayBeyondStepCounts(ch); // Except no key event // Set volume int vol = step.getVolume(); if (step.hasVolume() && vol < bt_defs::NSTEP_FM_VOLUME) { opnaCtrl_->setVolumeFM(ch, vol); } // Set instrument if (step.hasInstrument()) { if (auto inst = std::dynamic_pointer_cast( instMan_.lock()->getInstrumentSharedPtr(step.getInstrumentNumber()))) opnaCtrl_->setInstrumentFM(ch, inst); } else { opnaCtrl_->restoreFMEnvelopeFromReset(ch); } // Set effect executeStoredEffectsFM(ch); // Set key int noteNum = step.getNoteNumber(); switch (noteNum) { case Step::NOTE_NONE: if (!calledByNoteDelay) { // When this is called by note delay, skip delay check because it has already checked. checkFMDelayEventsInTick(step, ch); } opnaCtrl_->tickEvent(SoundSource::FM, ch); break; case Step::NOTE_KEY_OFF: opnaCtrl_->keyOffFM(ch); break; case Step::NOTE_KEY_CUT: opnaCtrl_->resetFMChannelEnvelope(ch); break; case Step::NOTE_ECHO0: opnaCtrl_->keyOnFM(ch, 0); break; case Step::NOTE_ECHO1: opnaCtrl_->keyOnFM(ch, 1); break; case Step::NOTE_ECHO2: opnaCtrl_->keyOnFM(ch, 2); break; case Step::NOTE_ECHO3: opnaCtrl_->keyOnFM(ch, 3); break; default: // Key on opnaCtrl_->keyOnFM(ch, Note(noteNum)); break; } } void PlaybackManager::executeSSGStepEvents(const Step& step, int ch, bool calledByNoteDelay) { if (!calledByNoteDelay && !step.isEmptyNote()) clearSSGDelayBeyondStepCounts(ch); // Except no key event // Set volume int vol = step.getVolume(); if (step.hasVolume() && vol < bt_defs::NSTEP_SSG_VOLUME) { opnaCtrl_->setVolumeSSG(ch, vol); } // Set instrument if (step.hasInstrument()) { if (auto inst = std::dynamic_pointer_cast( instMan_.lock()->getInstrumentSharedPtr(step.getInstrumentNumber()))) opnaCtrl_->setInstrumentSSG(ch, inst); } // Set effect executeStoredEffectsSSG(ch); // Set key int noteNum = step.getNoteNumber(); switch (noteNum) { case Step::NOTE_NONE: if (!calledByNoteDelay) { // When this is called by note delay, skip delay check because it has already checked. checkSSGDelayEventsInTick(step, ch); } opnaCtrl_->tickEvent(SoundSource::SSG, ch); break; case Step::NOTE_KEY_OFF: opnaCtrl_->keyOffSSG(ch); break; case Step::NOTE_KEY_CUT: opnaCtrl_->setNoteCutSSG(ch); break; case Step::NOTE_ECHO0: opnaCtrl_->keyOnSSG(ch, 0); break; case Step::NOTE_ECHO1: opnaCtrl_->keyOnSSG(ch, 1); break; case Step::NOTE_ECHO2: opnaCtrl_->keyOnSSG(ch, 2); break; case Step::NOTE_ECHO3: opnaCtrl_->keyOnSSG(ch, 3); break; default: // Key on opnaCtrl_->keyOnSSG(ch, Note(noteNum)); break; } } void PlaybackManager::executeRhythmStepEvents(const Step& step, int ch, bool calledByNoteDelay) { if (!calledByNoteDelay && !step.isEmptyNote()) clearRhythmDelayBeyondStepCounts(ch); // Except no key event // Set volume int vol = step.getVolume(); if (step.hasVolume() && vol < bt_defs::NSTEP_RHYTHM_VOLUME) { opnaCtrl_->setVolumeRhythm(ch, vol); } // Set effect executeStoredEffectsRhythm(ch); // Set key switch (step.getNoteNumber()) { case Step::NOTE_NONE: if (!calledByNoteDelay) { // When this is called by note delay, skip delay check because it has already checked. checkRhythmDelayEventsInTick(step, ch); } opnaCtrl_->tickEvent(SoundSource::RHYTHM, ch); break; case Step::NOTE_KEY_OFF: case Step::NOTE_KEY_CUT: opnaCtrl_->setKeyOffFlagRhythm(ch); break; default: // Key on & Echo opnaCtrl_->setKeyOnFlagRhythm(ch); break; } } void PlaybackManager::executeADPCMStepEvents(const Step& step, bool calledByNoteDelay) { if (!calledByNoteDelay && !step.isEmptyNote()) clearADPCMDelayBeyondStepCounts(); // Except no key event // Set volume int vol = step.getVolume(); if (step.hasVolume() && vol < bt_defs::NSTEP_ADPCM_VOLUME) { opnaCtrl_->setVolumeADPCM(vol); } // Set instrument if (step.hasInstrument()) { if (auto inst = instMan_.lock()->getInstrumentSharedPtr(step.getInstrumentNumber())) { if (inst->getType() == InstrumentType::ADPCM) opnaCtrl_->setInstrumentADPCM(std::dynamic_pointer_cast(inst)); else if (inst->getType() == InstrumentType::Drumkit) opnaCtrl_->setInstrumentDrumkit(std::dynamic_pointer_cast(inst)); } } // Set effect executeStoredEffectsADPCM(); // Set key int noteNum = step.getNoteNumber(); switch (noteNum) { case Step::NOTE_NONE: if (!calledByNoteDelay) { // When this is called by note delay, skip delay check because it has already checked. checkADPCMDelayEventsInTick(step); } opnaCtrl_->tickEvent(SoundSource::ADPCM, 0); break; case Step::NOTE_KEY_OFF: opnaCtrl_->keyOffADPCM(); break; case Step::NOTE_KEY_CUT: opnaCtrl_->setNoteCutADPCM(); break; case Step::NOTE_ECHO0: opnaCtrl_->keyOnADPCM(0); break; case Step::NOTE_ECHO1: opnaCtrl_->keyOnADPCM(1); break; case Step::NOTE_ECHO2: opnaCtrl_->keyOnADPCM(2); break; case Step::NOTE_ECHO3: opnaCtrl_->keyOnADPCM(3); break; default: // Key on opnaCtrl_->keyOnADPCM(Note(noteNum)); break; } } bool PlaybackManager::executeStoredEffectsGlobal() { bool changedNextPos = false; // Read step end based effects for (const Effect& eff : posChangeEffMem_) { switch (eff.type) { case EffectType::PositionJump: if (!(playStateFlags_ & PlayStateFlag::LoopPattern)) { // Skip when loop pattern changedNextPos |= effPositionJump(eff.value); } break; case EffectType::SongEnd: if (!(playStateFlags_ & PlayStateFlag::LoopPattern)) { // Skip when loop pattern effSongEnd(); changedNextPos = true; } break; case EffectType::PatternBreak: if (!(playStateFlags_ & PlayStateFlag::LoopPattern)) { // Skip when loop pattern changedNextPos |= effPatternBreak(eff.value); } break; default: break; } } posChangeEffMem_.clear(); // Read step beginning based effects for (const Effect& eff : playbackSpeedEffMem_) { switch (eff.type) { case EffectType::SpeedTempoChange: if (eff.value < 0x20) effSpeedChange(eff.value); else effTempoChange(eff.value); break; case EffectType::Groove: if (eff.value < static_cast(mod_.lock()->getGrooveCount())) effGrooveChange(eff.value); break; default: break; } } playbackSpeedEffMem_.clear(); return changedNextPos; } void PlaybackManager::storeEffectToMapFM(int ch, const Effect& eff) { switch (eff.type) { case EffectType::Arpeggio: case EffectType::PortamentoUp: case EffectType::PortamentoDown: case EffectType::TonePortamento: case EffectType::Vibrato: case EffectType::Tremolo: case EffectType::Pan: case EffectType::VolumeSlide: case EffectType::Detune: case EffectType::FineDetune: case EffectType::NoteSlideUp: case EffectType::NoteSlideDown: case EffectType::NoteRelease: case EffectType::TransposeDelay: case EffectType::VolumeDelay: case EffectType::FBControl: case EffectType::TLControl: case EffectType::MLControl: case EffectType::ARControl: case EffectType::DRControl: case EffectType::RRControl: case EffectType::Brightness: case EffectType::NoteCut: case EffectType::Retrigger: case EffectType::XVolumeSlide: effOnKeyOnMem_[SoundSource::FM].at(static_cast(ch)).enqueue(eff); break; case EffectType::SpeedTempoChange: case EffectType::Groove: playbackSpeedEffMem_.enqueue(eff); break; case EffectType::NoteDelay: effOnStepBeginMem_[SoundSource::FM].at(static_cast(ch)).enqueue(eff); break; case EffectType::PositionJump: case EffectType::SongEnd: case EffectType::PatternBreak: posChangeEffMem_.enqueue(eff); break; default: storeDirectRegisterSetEffectToQueue(SoundSource::FM, ch, eff); break; } } void PlaybackManager::executeStoredEffectsFM(int ch) { size_t uch = static_cast(ch); bool isNoteDelay = false; // Read step beginning based effects auto& stepBeginBasedEffs = effOnStepBeginMem_[SoundSource::FM].at(uch); for (const auto& eff : stepBeginBasedEffs) { switch (eff.type) { case EffectType::NoteDelay: ntDlyCntFM_.at(uch) = eff.value; isNoteDelay = true; break; default: break; } } stepBeginBasedEffs.clear(); // Read note on and step beginning based effects if (!isNoteDelay) { auto& keyOnBasedEffs = effOnKeyOnMem_[SoundSource::FM].at(uch); for (auto& eff : keyOnBasedEffs) { switch (eff.type) { case EffectType::Arpeggio: opnaCtrl_->setArpeggioEffectFM(ch, eff.value >> 4, eff.value & 0x0f); break; case EffectType::PortamentoUp: opnaCtrl_->setPortamentoEffectFM(ch, eff.value); break; case EffectType::PortamentoDown: opnaCtrl_->setPortamentoEffectFM(ch, -eff.value); break; case EffectType::TonePortamento: opnaCtrl_->setPortamentoEffectFM(ch, eff.value, true); break; case EffectType::Vibrato: opnaCtrl_->setVibratoEffectFM(ch, eff.value >> 4, eff.value & 0x0f); break; case EffectType::Tremolo: opnaCtrl_->setTremoloEffectFM(ch, eff.value >> 4, eff.value & 0x0f); break; case EffectType::Pan: if (-1 < eff.value && eff.value < 4) opnaCtrl_->setPanFM(ch, eff.value); break; case EffectType::VolumeSlide: { int hi = eff.value >> 4; int low = eff.value & 0x0f; if (hi && !low) opnaCtrl_->setVolumeSlideFM(ch, hi, true); // Slide up else if (!hi) opnaCtrl_->setVolumeSlideFM(ch, low, false); // Slide down break; } case EffectType::Detune: opnaCtrl_->setDetuneFM(ch, eff.value - 0x80); break; case EffectType::FineDetune: opnaCtrl_->setFineDetuneFM(ch, eff.value - 0x80); break; case EffectType::NoteSlideUp: opnaCtrl_->setNoteSlideFM(ch, eff.value >> 4, eff.value & 0x0f); break; case EffectType::NoteSlideDown: opnaCtrl_->setNoteSlideFM(ch, eff.value >> 4, -(eff.value & 0x0f)); break; case EffectType::NoteRelease: ntReleaseDlyCntFM_.at(uch) = eff.value; break; case EffectType::TransposeDelay: tposeDlyCntFM_.at(uch) = (eff.value & 0x70) >> 4; tposeDlyValueFM_.at(uch) = ((eff.value & 0x80) ? -1 : 1) * (eff.value & 0x0f); break; case EffectType::VolumeDelay: volDlyCntFM_.at(uch) = eff.value >> 8; volDlyValueFM_.at(uch) = eff.value & 0x00ff; break; case EffectType::FBControl: if (-1 < eff.value && eff.value < 8) opnaCtrl_->setFBControlFM(ch, eff.value); break; case EffectType::TLControl: { int op = eff.value >> 8; int val = eff.value & 0x00ff; if (0 < op && op < 5 && -1 < val && val < 128) opnaCtrl_->setTLControlFM(ch, op - 1, val); break; } case EffectType::MLControl: { int op = eff.value >> 4; int val = eff.value & 0x0f; if (0 < op && op < 5 && -1 < val && val < 16) opnaCtrl_->setMLControlFM(ch, op - 1, val); break; } case EffectType::ARControl: { int op = eff.value >> 8; int val = eff.value & 0x00ff; if (0 < op && op < 5 && -1 < val && val < 32) opnaCtrl_->setARControlFM(ch, op - 1, val); break; } case EffectType::DRControl: { int op = eff.value >> 8; int val = eff.value & 0x00ff; if (0 < op && op < 5 && -1 < val && val < 32) opnaCtrl_->setDRControlFM(ch, op - 1, val); break; } case EffectType::RRControl: { int op = eff.value >> 4; int val = eff.value & 0x0f; if (0 < op && op < 5 && -1 < val && val < 16) opnaCtrl_->setRRControlFM(ch, op - 1, val); break; } case EffectType::Brightness: { if (0 < eff.value) opnaCtrl_->setBrightnessFM(ch, eff.value - 0x80); break; } case EffectType::NoteCut: ntCutDlyCntFM_.at(uch) = eff.value; break; case EffectType::Retrigger: { int cnt = eff.value & 0x0f; if (cnt) { rtrgCntFM_.at(uch) = 0; rtrgCntValueFM_.at(uch) = cnt; rtrgVolValueFM_.at(uch) = ((eff.value & 0x80) ? 1 : -1) * ((eff.value & 0x70) >> 4); // Reverse } break; } case EffectType::XVolumeSlide: { int factor = (eff.value >> 4) - (eff.value & 0x0f); opnaCtrl_->setXVolumeSlideFM(ch, factor); break; } default: break; } } keyOnBasedEffs.clear(); executeDirectRegisterSetEffect(directRegisterSets_[SoundSource::FM].at(uch)); } } void PlaybackManager::storeEffectToMapSSG(int ch, const Effect& eff) { switch (eff.type) { case EffectType::Arpeggio: case EffectType::PortamentoUp: case EffectType::PortamentoDown: case EffectType::TonePortamento: case EffectType::Vibrato: case EffectType::Tremolo: case EffectType::VolumeSlide: case EffectType::Detune: case EffectType::FineDetune: case EffectType::NoteSlideUp: case EffectType::NoteSlideDown: case EffectType::NoteRelease: case EffectType::TransposeDelay: case EffectType::ToneNoiseMix: case EffectType::NoisePitch: case EffectType::VolumeDelay: case EffectType::HardEnvHighPeriod: case EffectType::HardEnvLowPeriod: case EffectType::AutoEnvelope: case EffectType::NoteCut: case EffectType::Retrigger: case EffectType::XVolumeSlide: effOnKeyOnMem_[SoundSource::SSG].at(static_cast(ch)).enqueue(eff); break; case EffectType::SpeedTempoChange: case EffectType::Groove: playbackSpeedEffMem_.enqueue(eff); break; case EffectType::NoteDelay: effOnStepBeginMem_[SoundSource::SSG].at(static_cast(ch)).enqueue(eff); break; case EffectType::PositionJump: case EffectType::SongEnd: case EffectType::PatternBreak: posChangeEffMem_.enqueue(eff); break; default: storeDirectRegisterSetEffectToQueue(SoundSource::SSG, ch, eff); break; } } void PlaybackManager::executeStoredEffectsSSG(int ch) { size_t uch = static_cast(ch); bool isNoteDelay = false; // Read step beginning based effects auto& stepBeginBasedEffs = effOnStepBeginMem_[SoundSource::SSG].at(uch); for (const auto& eff : stepBeginBasedEffs) { switch (eff.type) { case EffectType::NoteDelay: ntDlyCntSSG_.at(uch) = eff.value; isNoteDelay = true; break; default: break; } } stepBeginBasedEffs.clear(); // Read note on and step beginning based effects if (!isNoteDelay) { auto& keyOnBasedEffs = effOnKeyOnMem_[SoundSource::SSG].at(uch); for (const auto& eff : keyOnBasedEffs) { switch (eff.type) { case EffectType::Arpeggio: opnaCtrl_->setArpeggioEffectSSG(ch, eff.value >> 4, eff.value & 0x0f); break; case EffectType::PortamentoUp: opnaCtrl_->setPortamentoEffectSSG(ch, eff.value); break; case EffectType::PortamentoDown: opnaCtrl_->setPortamentoEffectSSG(ch, -eff.value); break; case EffectType::TonePortamento: opnaCtrl_->setPortamentoEffectSSG(ch, eff.value, true); break; case EffectType::Vibrato: opnaCtrl_->setVibratoEffectSSG(ch, eff.value >> 4, eff.value & 0x0f); break; case EffectType::Tremolo: opnaCtrl_->setTremoloEffectSSG(ch, eff.value >> 4, eff.value & 0x0f); break; case EffectType::VolumeSlide: { int hi = eff.value >> 4; int low = eff.value & 0x0f; if (hi && !low) opnaCtrl_->setVolumeSlideSSG(ch, hi, true); // Slide up else if (!hi) opnaCtrl_->setVolumeSlideSSG(ch, low, false); // Slide down break; } case EffectType::Detune: opnaCtrl_->setDetuneSSG(ch, eff.value - 0x80); break; case EffectType::FineDetune: opnaCtrl_->setFineDetuneSSG(ch, eff.value - 0x80); break; case EffectType::NoteSlideUp: opnaCtrl_->setNoteSlideSSG(ch, eff.value >> 4, eff.value & 0x0f); break; case EffectType::NoteSlideDown: opnaCtrl_->setNoteSlideSSG(ch, eff.value >> 4, -(eff.value & 0x0f)); break; case EffectType::NoteRelease: ntReleaseDlyCntSSG_.at(uch) = eff.value; break; case EffectType::TransposeDelay: tposeDlyCntSSG_.at(uch) = (eff.value & 0x70) >> 4; tposeDlyValueSSG_.at(uch) = ((eff.value & 0x80) ? -1 : 1) * (eff.value & 0x0f); break; case EffectType::ToneNoiseMix: if (-1 < eff.value && eff.value < 4) opnaCtrl_->setToneNoiseMixSSG(ch, eff.value); break; case EffectType::NoisePitch: if (-1 < eff.value && eff.value < 32) opnaCtrl_->setNoisePitchSSG(ch, eff.value); break; case EffectType::HardEnvHighPeriod: opnaCtrl_->setHardEnvelopePeriod(ch, true, eff.value); break; case EffectType::HardEnvLowPeriod: opnaCtrl_->setHardEnvelopePeriod(ch, false, eff.value); break; case EffectType::VolumeDelay: volDlyCntSSG_.at(uch) = eff.value >> 8; volDlyValueSSG_.at(uch) = eff.value & 0x00ff; break; case EffectType::AutoEnvelope: opnaCtrl_->setAutoEnvelopeSSG(ch, (eff.value >> 4) - 8, eff.value & 0x0f); break; case EffectType::NoteCut: ntCutDlyCntSSG_.at(uch) = eff.value; break; case EffectType::Retrigger: { int cnt = eff.value & 0x0f; if (cnt) { rtrgCntSSG_.at(uch) = 0; rtrgCntValueSSG_.at(uch) = cnt; rtrgVolValueSSG_.at(uch) = ((eff.value & 0x80) ? -1 : 1) * ((eff.value & 0x70) >> 4); } break; } case EffectType::XVolumeSlide: { int factor = (eff.value >> 4) - (eff.value & 0x0f); opnaCtrl_->setXVolumeSlideSSG(ch, factor); break; } default: break; } } keyOnBasedEffs.clear(); executeDirectRegisterSetEffect(directRegisterSets_[SoundSource::SSG].at(uch)); } } void PlaybackManager::storeEffectToMapRhythm(int ch, const Effect& eff) { switch (eff.type) { case EffectType::Pan: case EffectType::NoteRelease: case EffectType::MasterVolume: case EffectType::VolumeDelay: case EffectType::NoteCut: case EffectType::Retrigger: effOnKeyOnMem_[SoundSource::RHYTHM].at(static_cast(ch)).enqueue(eff); break; case EffectType::SpeedTempoChange: case EffectType::Groove: playbackSpeedEffMem_.enqueue(eff); break; case EffectType::NoteDelay: effOnStepBeginMem_[SoundSource::RHYTHM].at(static_cast(ch)).enqueue(eff); break; case EffectType::PositionJump: case EffectType::SongEnd: case EffectType::PatternBreak: posChangeEffMem_.enqueue(eff); break; default: storeDirectRegisterSetEffectToQueue(SoundSource::RHYTHM, ch, eff); break; } } void PlaybackManager::executeStoredEffectsRhythm(int ch) { size_t uch = static_cast(ch); bool isNoteDelay = false; // Read step beginning based effects auto& stepBeginBasedEffs = effOnStepBeginMem_[SoundSource::RHYTHM].at(uch); for (const auto& eff : stepBeginBasedEffs) { switch (eff.type) { case EffectType::NoteDelay: ntDlyCntRhythm_.at(uch) = eff.value; isNoteDelay = true; break; default: break; } } stepBeginBasedEffs.clear(); // Read key on and step beginning based effects if (!isNoteDelay) { auto& keyOnBasedEffs = effOnKeyOnMem_[SoundSource::RHYTHM].at(uch); for (const auto& eff : keyOnBasedEffs) { switch (eff.type) { case EffectType::Pan: if (-1 < eff.value && eff.value < 4) opnaCtrl_->setPanRhythm(ch, eff.value); break; case EffectType::NoteRelease: ntReleaseDlyCntRhythm_.at(uch) = eff.value; break; case EffectType::MasterVolume: if (-1 < eff.value && eff.value < 64) opnaCtrl_->setMasterVolumeRhythm(eff.value); break; case EffectType::VolumeDelay: { int count = eff.value >> 8; if (count > 0) { volDlyCntRhythm_.at(uch) = count; volDlyValueRhythm_.at(uch) = eff.value & 0x00ff; } break; } case EffectType::NoteCut: ntCutDlyCntRhythm_.at(uch) = eff.value; break; case EffectType::Retrigger: { int cnt = eff.value & 0x0f; if (cnt) { rtrgCntRhythm_.at(uch) = 0; rtrgCntValueRhythm_.at(uch) = cnt; rtrgVolValueRhythm_.at(uch) = ((eff.value & 0x80) ? -1 : 1) * ((eff.value & 0x70) >> 4); } break; } default: break; } } keyOnBasedEffs.clear(); executeDirectRegisterSetEffect(directRegisterSets_[SoundSource::RHYTHM].at(uch)); } } void PlaybackManager::storeEffectToMapADPCM(int ch, const Effect& eff) { switch (eff.type) { case EffectType::Arpeggio: case EffectType::PortamentoUp: case EffectType::PortamentoDown: case EffectType::TonePortamento: case EffectType::Vibrato: case EffectType::Tremolo: case EffectType::Pan: case EffectType::VolumeSlide: case EffectType::Detune: case EffectType::FineDetune: case EffectType::NoteSlideUp: case EffectType::NoteSlideDown: case EffectType::NoteRelease: case EffectType::TransposeDelay: case EffectType::VolumeDelay: case EffectType::NoteCut: case EffectType::Retrigger: case EffectType::XVolumeSlide: effOnKeyOnMem_[SoundSource::ADPCM].front().enqueue(eff); break; case EffectType::SpeedTempoChange: case EffectType::Groove: playbackSpeedEffMem_.enqueue(eff); break; case EffectType::NoteDelay: effOnStepBeginMem_[SoundSource::ADPCM].front().enqueue(eff); break; case EffectType::PositionJump: case EffectType::SongEnd: case EffectType::PatternBreak: posChangeEffMem_.enqueue(eff); break; default: storeDirectRegisterSetEffectToQueue(SoundSource::ADPCM, ch, eff); break; } } void PlaybackManager::executeStoredEffectsADPCM() { bool isNoteDelay = false; // Read step beginning based effects auto& stepBeginBasedEffs = effOnStepBeginMem_[SoundSource::ADPCM].front(); for (const auto& eff : stepBeginBasedEffs) { switch (eff.type) { case EffectType::NoteDelay: ntDlyCntADPCM_ = eff.value; isNoteDelay = true; break; default: break; } } stepBeginBasedEffs.clear(); // Read note on and step beginning based effects if (!isNoteDelay) { auto& keyOnBasedEffs = effOnKeyOnMem_[SoundSource::ADPCM].front(); for (const auto& eff : keyOnBasedEffs) { switch (eff.type) { case EffectType::Arpeggio: opnaCtrl_->setArpeggioEffectADPCM(eff.value >> 4, eff.value & 0x0f); break; case EffectType::PortamentoUp: opnaCtrl_->setPortamentoEffectADPCM(eff.value); break; case EffectType::PortamentoDown: opnaCtrl_->setPortamentoEffectADPCM(-eff.value); break; case EffectType::TonePortamento: opnaCtrl_->setPortamentoEffectADPCM(eff.value, true); break; case EffectType::Vibrato: opnaCtrl_->setVibratoEffectADPCM(eff.value >> 4, eff.value & 0x0f); break; case EffectType::Tremolo: opnaCtrl_->setTremoloEffectADPCM(eff.value >> 4, eff.value & 0x0f); break; case EffectType::Pan: if (-1 < eff.value && eff.value < 4) opnaCtrl_->setPanADPCM(eff.value); break; case EffectType::VolumeSlide: { int hi = eff.value >> 4; int low = eff.value & 0x0f; if (hi && !low) opnaCtrl_->setVolumeSlideADPCM(hi, true); // Slide up else if (!hi) opnaCtrl_->setVolumeSlideADPCM(low, false); // Slide down break; } case EffectType::Detune: opnaCtrl_->setDetuneADPCM(eff.value - 0x80); break; case EffectType::FineDetune: opnaCtrl_->setFineDetuneADPCM(eff.value - 0x80); break; case EffectType::NoteSlideUp: opnaCtrl_->setNoteSlideADPCM(eff.value >> 4, eff.value & 0x0f); break; case EffectType::NoteSlideDown: opnaCtrl_->setNoteSlideADPCM(eff.value >> 4, -(eff.value & 0x0f)); break; case EffectType::NoteRelease: ntReleaseDlyCntADPCM_ = eff.value; break; case EffectType::TransposeDelay: tposeDlyCntADPCM_ = (eff.value & 0x70) >> 4; tposeDlyValueADPCM_ = ((eff.value & 0x80) ? -1 : 1) * ((eff.value & 0x70) >> 4); break; case EffectType::VolumeDelay: volDlyCntADPCM_ = eff.value >> 8; volDlyValueADPCM_ = eff.value & 0x00ff; break; case EffectType::NoteCut: ntCutDlyCntADPCM_ = eff.value; break; case EffectType::Retrigger: { int cnt = eff.value & 0x0f; if (cnt) { rtrgCntADPCM_ = 0; rtrgCntValueADPCM_ = cnt; rtrgVolValueADPCM_ = ((eff.value & 0x80) ? -1 : 1) * (eff.value & 0x0f); } break; } case EffectType::XVolumeSlide: { int factor = (eff.value >> 4) - (eff.value & 0x0f); opnaCtrl_->setXVolumeSlideADPCM(factor); break; } default: break; } } keyOnBasedEffs.clear(); executeDirectRegisterSetEffect(directRegisterSets_[SoundSource::ADPCM].front()); } } void PlaybackManager::storeDirectRegisterSetEffectToQueue(SoundSource src, int ch, const Effect& eff) { size_t uch = static_cast(ch); switch (eff.type) { case EffectType::RegisterAddress0: if (-1 < eff.value && eff.value < 0x6c) { directRegisterSets_.at(src).at(uch).push_back({ eff.value, 0, false }); } break; case EffectType::RegisterAddress1: if (-1 < eff.value && eff.value < 0x6c) { directRegisterSets_.at(src).at(uch).push_back({ 0x100 | eff.value, 0, false }); } break; case EffectType::RegisterValue: { DirectRegisterSetQueue& queue = directRegisterSets_.at(src).at(uch); if (!queue.empty() && -1 < eff.value) { RegisterUnit& unit = queue.back(); unit.value = eff.value; unit.hasCompleted = true; } break; } default: break; } } void PlaybackManager::executeDirectRegisterSetEffect(DirectRegisterSetQueue& queue) { for (const RegisterUnit& unit : queue) { if (unit.hasCompleted) opnaCtrl_->sendRegister(unit.address, unit.value); } queue.clear(); } bool PlaybackManager::effPositionJump(int nextOrder) { if (nextOrder < static_cast(getOrderSize(curSongNum_))) { nextReadPos_.set(nextOrder, 0); return true; } return false; } void PlaybackManager::effSongEnd() { nextReadPos_.invalidate(); } bool PlaybackManager::effPatternBreak(int nextStep) { if (playingPos_.order == static_cast(getOrderSize(curSongNum_)) - 1 && nextStep < static_cast(getPatternSizeFromOrderNumber(curSongNum_, 0))) { nextReadPos_.set(0, nextStep); return true; } else if (nextStep < static_cast(getPatternSizeFromOrderNumber(curSongNum_, playingPos_.order + 1))) { nextReadPos_.set(playingPos_.order + 1, nextStep); return true; } return false; } void PlaybackManager::effSpeedChange(int speed) { tickCounter_.lock()->setSpeed(speed ? speed : 1); tickCounter_.lock()->setGrooveState(GrooveState::Invalid); } void PlaybackManager::effTempoChange(int tempo) { auto tc = tickCounter_.lock(); if (tc->getGrooveEnabled() || tc->getTempo() != tempo) { tc->setTempo(tempo); tc->setGrooveState(GrooveState::Invalid); } } void PlaybackManager::effGrooveChange(int num) { tickCounter_.lock()->setGroove(mod_.lock()->getGroove(num)); tickCounter_.lock()->setGrooveState(GrooveState::ValidByLocal); } void PlaybackManager::tickProcess(int rest) { if (!(playStateFlags_ & PlayStateFlag::ReadFirstStep)) return; // When it has not read first step updateDelayEventCounts(); auto& song = mod_.lock()->getSong(curSongNum_); for (auto& attrib : songStyle_.trackAttribs) { auto& curStep = song.getTrack(attrib.number) .getPatternFromOrderNumber(playingPos_.order).getStep(playingPos_.step); int ch = attrib.channelInSource; bool hasDoneNoteDelay = false; switch (attrib.source) { case SoundSource::FM: hasDoneNoteDelay = checkFMDelayEventsInTick(curStep, ch); break; case SoundSource::SSG: hasDoneNoteDelay = checkSSGDelayEventsInTick(curStep, ch); break; case SoundSource::RHYTHM: hasDoneNoteDelay = checkRhythmDelayEventsInTick(curStep, ch); break; case SoundSource::ADPCM: hasDoneNoteDelay = checkADPCMDelayEventsInTick(curStep); break; } if (rest == 1 && nextReadPos_.isValid() && attrib.source == SoundSource::FM && !isPlayingStep()) { // Channel envelope reset before next key on auto& step = song.getTrack(attrib.number) .getPatternFromOrderNumber(nextReadPos_.order).getStep(nextReadPos_.step); bool hasNoteDelay = false; for (int i = 0; i < Step::N_EFFECT; ++i) { auto&& eff = effect_utils::validateEffect(attrib.source, step.getEffect(i)); if (eff.type == EffectType::NoteDelay && eff.value > 0) { // Note delay check opnaCtrl_->tickEvent(attrib.source, ch); hasNoteDelay = true; break; } } // Skip the statement below if envelope reset effect has cexecuted if (!hasNoteDelay && ntCutDlyCntFM_.at(static_cast(ch))) envelopeResetEffectFM(step, ch); } else if (!hasDoneNoteDelay) { // Skip tick process when step process was called by note delay event. opnaCtrl_->tickEvent(attrib.source, ch); } } opnaCtrl_->updateRegisterStates(); } bool PlaybackManager::checkFMDelayEventsInTick(const Step& step, int ch) { size_t uch = static_cast(ch); // Check volume delay if (!volDlyCntFM_.at(uch)) opnaCtrl_->setOneshotVolumeFM(ch, volDlyValueFM_.at(uch)); // Check note release if (!ntReleaseDlyCntFM_.at(uch)) opnaCtrl_->keyOffFM(ch); // Check transpose delay if (!tposeDlyCntFM_.at(uch)) opnaCtrl_->setTransposeEffectFM(ch, tposeDlyValueFM_.at(uch)); // Check envelope reset delay if (!ntCutDlyCntFM_.at(uch)) opnaCtrl_->resetFMChannelEnvelope(ch); // Check retrigger if (!rtrgCntFM_.at(uch)) opnaCtrl_->retriggerKeyOnFM(ch, rtrgVolValueFM_.at(uch)); // Check note delay and envelope reset return checkFMNoteDelayAndEnvelopeReset(step, ch); } bool PlaybackManager::checkFMNoteDelayAndEnvelopeReset(const Step& step, int ch) { int cnt = ntDlyCntFM_.at(static_cast(ch)); if (!cnt) { executeFMStepEvents(step, ch, true); return true; } // Skip the statement below if envelope reset effect has cexecuted if (cnt == 1 && ntCutDlyCntFM_.at(static_cast(ch))) { // Channel envelope reset before next key on envelopeResetEffectFM(step, ch); } return false; } void PlaybackManager::envelopeResetEffectFM(const Step& step, int ch) { if (!(step.isEmptyNote() || step.hasKeyOff()) && opnaCtrl_->enableFMEnvelopeReset(ch)) { // Key on or echo buffer access for (int i = 0; i < Step::N_EFFECT; ++i) { auto&& eff = effect_utils::validateEffect(SoundSource::FM, step.getEffect(i)); // "SoundSource::FM" is dummy if (eff.type == EffectType::TonePortamento) { if (eff.value) opnaCtrl_->tickEvent(SoundSource::FM, ch); else opnaCtrl_->resetFMChannelEnvelope(ch); return; } } if (opnaCtrl_->isTonePortamentoFM(ch)) opnaCtrl_->tickEvent(SoundSource::FM, ch); else opnaCtrl_->resetFMChannelEnvelope(ch); } else { opnaCtrl_->tickEvent(SoundSource::FM, ch); } } bool PlaybackManager::checkSSGDelayEventsInTick(const Step& step, int ch) { size_t uch = static_cast(ch); // Check volume delay if (!volDlyCntSSG_.at(uch)) opnaCtrl_->setOneshotVolumeSSG(ch, volDlyValueSSG_.at(uch)); // Check note release if (!ntReleaseDlyCntSSG_.at(uch)) opnaCtrl_->keyOffSSG(ch); // Check note cut if (!ntCutDlyCntSSG_.at(uch)) opnaCtrl_->setNoteCutSSG(ch); // Check transpose delay if (!tposeDlyCntSSG_.at(uch)) opnaCtrl_->setTransposeEffectSSG(ch, tposeDlyValueSSG_.at(uch)); // Check retrigger if (!rtrgCntSSG_.at(uch)) opnaCtrl_->retriggerKeyOnSSG(ch, rtrgVolValueSSG_.at(uch)); // Check note delay if (!ntDlyCntSSG_.at(uch)) { executeSSGStepEvents(step, ch, true); return true; } return false; } bool PlaybackManager::checkRhythmDelayEventsInTick(const Step& step, int ch) { size_t uch = static_cast(ch); // Check volume delay if (!volDlyCntRhythm_.at(uch)) opnaCtrl_->setOneshotVolumeRhythm(ch, volDlyValueRhythm_.at(uch)); // Check note release/cut if (!ntReleaseDlyCntRhythm_.at(uch) || !ntCutDlyCntRhythm_.at(uch)) opnaCtrl_->setKeyOffFlagRhythm(ch); // Check retrigger if (!rtrgCntRhythm_.at(uch)) opnaCtrl_->retriggerKeyOnFlagRhythm(ch, rtrgVolValueRhythm_.at(uch)); // Check note delay if (!ntDlyCntRhythm_.at(uch)) { executeRhythmStepEvents(step, ch, true); return true; } return false; } bool PlaybackManager::checkADPCMDelayEventsInTick(const Step& step) { // Check volume delay if (!volDlyCntADPCM_) opnaCtrl_->setOneshotVolumeADPCM(volDlyValueADPCM_); // Check note release if (!ntReleaseDlyCntADPCM_) opnaCtrl_->keyOffADPCM(); // Check note cut if (!ntCutDlyCntADPCM_) opnaCtrl_->setNoteCutADPCM(); // Check transpose delay if (!tposeDlyCntADPCM_) opnaCtrl_->setTransposeEffectADPCM(tposeDlyValueADPCM_); // Check retrigger if (!rtrgCntADPCM_) opnaCtrl_->retriggerKeyOnADPCM(rtrgVolValueADPCM_); // Check note delay if (!ntDlyCntADPCM_) { executeADPCMStepEvents(step, true); return true; } return false; } void PlaybackManager::clearEffectMaps() { playbackSpeedEffMem_.clear(); posChangeEffMem_.clear(); for (auto& maps: effOnKeyOnMem_) { for (EffectMemory& mem : maps.second) mem.clear(); } for (auto& maps: effOnStepBeginMem_) { for (EffectMemory& mem : maps.second) mem.clear(); } for (auto& maps: directRegisterSets_) { for (DirectRegisterSetQueue& queue : maps.second) queue.clear(); } } void PlaybackManager::clearDelayWithinStepCounts() { std::fill(ntDlyCntFM_.begin(), ntDlyCntFM_.end(), -1); std::fill(ntDlyCntSSG_.begin(), ntDlyCntSSG_.end(), -1); std::fill(ntDlyCntRhythm_.begin(), ntDlyCntRhythm_.end(), -1); ntDlyCntADPCM_ = -1; std::fill(rtrgCntFM_.begin(), rtrgCntFM_.end(), -1); std::fill(rtrgCntValueFM_.begin(), rtrgCntValueFM_.end(), -1); std::fill(rtrgCntSSG_.begin(), rtrgCntSSG_.end(), -1); std::fill(rtrgCntValueSSG_.begin(), rtrgCntValueSSG_.end(), -1); std::fill(rtrgCntRhythm_.begin(), rtrgCntRhythm_.end(), -1); std::fill(rtrgCntValueRhythm_.begin(), rtrgCntValueRhythm_.end(), -1); rtrgCntADPCM_ = -1; rtrgCntValueADPCM_ = -1; } void PlaybackManager::clearDelayBeyondStepCounts() { std::fill(ntReleaseDlyCntFM_.begin(), ntReleaseDlyCntFM_.end(), -1); std::fill(ntCutDlyCntFM_.begin(), ntCutDlyCntFM_.end(), -1); std::fill(volDlyCntFM_.begin(), volDlyCntFM_.end(), -1); std::fill(volDlyValueFM_.begin(), volDlyValueFM_.end(), -1); std::fill(tposeDlyCntFM_.begin(), tposeDlyCntFM_.end(), -1); std::fill(tposeDlyValueFM_.begin(), tposeDlyValueFM_.end(), 0); std::fill(ntReleaseDlyCntSSG_.begin(), ntReleaseDlyCntSSG_.end(), -1); std::fill(ntCutDlyCntSSG_.begin(), ntCutDlyCntSSG_.end(), -1); std::fill(volDlyCntSSG_.begin(), volDlyCntSSG_.end(), -1); std::fill(volDlyValueSSG_.begin(), volDlyValueSSG_.end(), -1); std::fill(tposeDlyCntSSG_.begin(), tposeDlyCntSSG_.end(), -1); std::fill(tposeDlyValueSSG_.begin(), tposeDlyValueSSG_.end(), 0); std::fill(ntReleaseDlyCntRhythm_.begin(), ntReleaseDlyCntRhythm_.end(), -1); std::fill(ntCutDlyCntRhythm_.begin(), ntCutDlyCntRhythm_.end(), -1); std::fill(volDlyCntRhythm_.begin(), volDlyCntRhythm_.end(), -1); std::fill(volDlyValueRhythm_.begin(), volDlyValueRhythm_.end(), -1); ntReleaseDlyCntADPCM_ = -1; ntCutDlyCntADPCM_ = -1; volDlyCntADPCM_ = -1; volDlyValueADPCM_ = -1; tposeDlyCntADPCM_ = -1; tposeDlyValueADPCM_ = 0; } void PlaybackManager::clearFMDelayBeyondStepCounts(int ch) { size_t uch = static_cast(ch); ntReleaseDlyCntFM_.at(uch) = -1; ntCutDlyCntFM_.at(uch) = -1; volDlyCntFM_.at(uch) = -1; volDlyValueFM_.at(uch) = -1; tposeDlyCntFM_.at(uch) = -1; tposeDlyValueFM_.at(uch) = 0; } void PlaybackManager::clearSSGDelayBeyondStepCounts(int ch) { size_t uch = static_cast(ch); ntReleaseDlyCntSSG_.at(uch) = -1; ntCutDlyCntSSG_.at(uch) = -1; volDlyCntSSG_.at(uch) = -1; volDlyValueSSG_.at(uch) = -1; tposeDlyCntSSG_.at(uch) = -1; tposeDlyValueSSG_.at(uch) = 0; } void PlaybackManager::clearRhythmDelayBeyondStepCounts(int ch) { size_t uch = static_cast(ch); ntReleaseDlyCntRhythm_.at(uch) = -1; ntCutDlyCntRhythm_.at(uch) = -1; volDlyCntRhythm_.at(uch) = -1; volDlyValueRhythm_.at(uch) = -1; } void PlaybackManager::clearADPCMDelayBeyondStepCounts() { ntReleaseDlyCntADPCM_ = -1; ntCutDlyCntADPCM_ = -1; volDlyCntADPCM_ = -1; volDlyValueADPCM_ = -1; tposeDlyCntADPCM_ = -1; tposeDlyValueADPCM_ = 0; } void PlaybackManager::updateDelayEventCounts() { static auto cd1 = [](int x) { return (x == -1) ? -1 : --x; }; std::transform(ntDlyCntFM_.begin(), ntDlyCntFM_.end(), ntDlyCntFM_.begin(), cd1); std::transform(ntDlyCntSSG_.begin(), ntDlyCntSSG_.end(), ntDlyCntSSG_.begin(), cd1); std::transform(ntDlyCntRhythm_.begin(), ntDlyCntRhythm_.end(), ntDlyCntRhythm_.begin(), cd1); --ntDlyCntADPCM_; std::transform(ntReleaseDlyCntFM_.begin(), ntReleaseDlyCntFM_.end(), ntReleaseDlyCntFM_.begin(), cd1); std::transform(ntReleaseDlyCntSSG_.begin(), ntReleaseDlyCntSSG_.end(), ntReleaseDlyCntSSG_.begin(), cd1); std::transform(ntReleaseDlyCntRhythm_.begin(), ntReleaseDlyCntRhythm_.end(), ntReleaseDlyCntRhythm_.begin(), cd1); --ntReleaseDlyCntADPCM_; std::transform(volDlyCntFM_.begin(), volDlyCntFM_.end(), volDlyCntFM_.begin(), cd1); std::transform(volDlyCntSSG_.begin(), volDlyCntSSG_.end(), volDlyCntSSG_.begin(), cd1); std::transform(volDlyCntRhythm_.begin(), volDlyCntRhythm_.end(), volDlyCntRhythm_.begin(), cd1); --volDlyCntADPCM_; std::transform(tposeDlyCntFM_.begin(), tposeDlyCntFM_.end(), tposeDlyCntFM_.begin(), cd1); std::transform(tposeDlyCntSSG_.begin(), tposeDlyCntSSG_.end(), tposeDlyCntSSG_.begin(), cd1); --tposeDlyCntADPCM_; std::transform(ntCutDlyCntFM_.begin(), ntCutDlyCntFM_.end(), ntCutDlyCntFM_.begin(), cd1); std::transform(ntCutDlyCntSSG_.begin(), ntCutDlyCntSSG_.end(), ntCutDlyCntSSG_.begin(), cd1); std::transform(ntCutDlyCntRhythm_.begin(), ntCutDlyCntRhythm_.end(), ntCutDlyCntRhythm_.begin(), cd1); --ntCutDlyCntADPCM_; static auto cd2 = [](int& cnt, int max) { if (cnt != -1) { if (cnt) --cnt; else cnt = max - 1; } }; for (size_t uch = 0; uch < rtrgCntFM_.size(); ++uch) cd2(rtrgCntFM_.at(uch), rtrgCntValueFM_.at(uch)); for (size_t uch = 0; uch < rtrgCntSSG_.size(); ++uch) cd2(rtrgCntSSG_.at(uch), rtrgCntValueSSG_.at(uch)); for (size_t uch = 0; uch < rtrgCntRhythm_.size(); ++uch) cd2(rtrgCntRhythm_.at(uch), rtrgCntValueRhythm_.at(uch)); cd2(rtrgCntADPCM_, rtrgCntValueADPCM_); } void PlaybackManager::checkPlayPosition(int maxStepSize) { if (isPlaySong() && playingPos_.step >= maxStepSize) { playingPos_.step = maxStepSize - 1; findNextStep(); } } void PlaybackManager::setChannelRetrieving(bool enabled) { isRetrieveChannel_ = enabled; } void PlaybackManager::retrieveChannelStates() { size_t fmch = Song::getFMChannelCount(songStyle_.type); // Skip echo buffer due to takes time to search std::vector isSetInstFM(fmch, false), isSetVolFM(fmch, false), isSetArpFM(fmch, false); std::vector isSetPrtFM(fmch, false), isSetVibFM(fmch, false), isSetTreFM(fmch, false); std::vector isSetPanFM(fmch, false), isSetVolSldFM(fmch, false), isSetDtnFM(fmch, false); std::vector isSetFBCtrlFM(fmch, false), isSetTLCtrlFM(fmch, false), isSetMLCtrlFM(fmch, false); std::vector isSetARCtrlFM(fmch, false), isSetDRCtrlFM(fmch, false), isSetRRCtrlFM(fmch, false); std::vector isSetBrightFM(fmch, false), isSetFiDtnFM(fmch, false), isSetXVolSldFM(fmch, false); std::vector isSetInstSSG(3, false), isSetVolSSG(3, false), isSetArpSSG(3, false), isSetPrtSSG(3, false); std::vector isSetVibSSG(3, false), isSetTreSSG(3, false), isSetVolSldSSG(3, false), isSetDtnSSG(3, false); std::vector isSetTNMixSSG(3, false), isSetFiDtnSSG(3, false), isSetXVolSldSSG(3, false); std::vector isSetVolRhythm(6, false), isSetPanRhythm(6, false); bool isSetInstADPCM(false), isSetVolADPCM(false), isSetArpADPCM(false), isSetPrtADPCM(false); bool isSetVibADPCM(false), isSetTreADPCM(false), isSetPanADPCM(false), isSetVolSldADPCM(false); bool isSetDtnADPCM(false), isSetFiDtnADPCM(false), isSetXVolSldADPCM(false); bool isSetMVolRhythm = false; bool isSetNoisePitchSSG = false; bool isSetHardEnvPeriodHighSSG = false; bool isSetHardEnvPeriodLowSSG = false; bool isSetAutoEnvSSG = false; /// bit0: step /// bit1: tempo /// bit2: groove uint8_t speedStates = 0; Position pos = playingPos_; bool isPrevPos = false; Song& song = mod_.lock()->getSong(curSongNum_); while (true) { for (auto it = songStyle_.trackAttribs.rbegin(), e = songStyle_.trackAttribs.rend(); it != e; ++it) { Step& step = song.getTrack(it->number).getPatternFromOrderNumber(pos.order).getStep(pos.step); int ch = it->channelInSource; size_t uch = static_cast(ch); switch (it->source) { case SoundSource::FM: { // Volume int vol = step.getVolume(); if (!isSetVolFM[uch] && step.hasVolume() && vol < bt_defs::NSTEP_FM_VOLUME) { isSetVolFM[uch] = true; if (isPrevPos) opnaCtrl_->setVolumeFM(ch, vol); } // Instrument if (!isSetInstFM[uch] && step.hasInstrument()) { if (auto inst = std::dynamic_pointer_cast( instMan_.lock()->getInstrumentSharedPtr(step.getInstrumentNumber()))) { isSetInstFM[uch] = true; if (isPrevPos) opnaCtrl_->setInstrumentFM(ch, inst); } } // Effects for (int i = Step::N_EFFECT - 1; i > -1; --i) { Effect eff = effect_utils::validateEffect(SoundSource::FM, step.getEffect(i)); switch (eff.type) { case EffectType::Arpeggio: if (!isSetArpFM[uch]) { isSetArpFM[uch] = true; if (isPrevPos) opnaCtrl_->setArpeggioEffectFM(ch, eff.value >> 4, eff.value & 0x0f); } break; case EffectType::PortamentoUp: if (!isSetPrtFM[uch]) { isSetPrtFM[uch] = true; if (isPrevPos) opnaCtrl_->setPortamentoEffectFM(ch, eff.value); } break; case EffectType::PortamentoDown: if (!isSetPrtFM[uch]) { isSetPrtFM[uch] = true; if (isPrevPos) opnaCtrl_->setPortamentoEffectFM(ch, -eff.value); } break; case EffectType::TonePortamento: if (!isSetPrtFM[uch]) { isSetPrtFM[uch] = true; if (isPrevPos) opnaCtrl_->setPortamentoEffectFM(ch, eff.value, true); } break; case EffectType::Vibrato: if (!isSetVibFM[uch]) { isSetVibFM[uch] = true; if (isPrevPos) opnaCtrl_->setVibratoEffectFM(ch, eff.value >> 4, eff.value & 0x0f); } break; case EffectType::Tremolo: if (!isSetTreFM[uch]) { isSetTreFM[uch] = true; if (isPrevPos) opnaCtrl_->setTremoloEffectFM(ch, eff.value >> 4, eff.value & 0x0f); } break; case EffectType::Pan: if (-1 < eff.value && eff.value < 4 && !isSetPanFM[uch]) { isSetPanFM[uch] = true; if (isPrevPos) opnaCtrl_->setPanFM(ch, eff.value); } break; case EffectType::VolumeSlide: if (!isSetVolSldFM[uch]) { isSetVolSldFM[uch] = true; if (isPrevPos) { int hi = eff.value >> 4; int low = eff.value & 0x0f; if (hi && !low) opnaCtrl_->setVolumeSlideFM(ch, hi, true); // Slide up else if (!hi) opnaCtrl_->setVolumeSlideFM(ch, low, false); // Slide down } } break; case EffectType::SpeedTempoChange: if (!(speedStates & 0x4)) { if (eff.value < 0x20) { // Speed change if (!(speedStates & 0x1)) { speedStates |= 0x1; if (isPrevPos) effSpeedChange(eff.value); } } else if (!(speedStates & 0x2)) { // Tempo change speedStates |= 0x2; if (isPrevPos) effTempoChange(eff.value); } } break; case EffectType::Groove: if (eff.value < static_cast(mod_.lock()->getGrooveCount()) && !speedStates) { speedStates |= 0x4; if (isPrevPos) effGrooveChange(eff.value); } break; case EffectType::Detune: if (!isSetDtnFM[uch]) { isSetDtnFM[uch] = true; if (isPrevPos) opnaCtrl_->setDetuneFM(ch, eff.value - 0x80); } break; case EffectType::FineDetune: if (!isSetFiDtnFM[uch]) { isSetFiDtnFM[uch] = true; if (isPrevPos) opnaCtrl_->setFineDetuneFM(ch, eff.value - 0x80); } break; case EffectType::FBControl: if (!isSetFBCtrlFM[uch]) { isSetFBCtrlFM[uch] = true; if (isPrevPos) { if (-1 < eff.value && eff.value < 8) opnaCtrl_->setFBControlFM(ch, eff.value); } } break; case EffectType::TLControl: if (!isSetTLCtrlFM[uch]) { isSetTLCtrlFM[uch] = true; if (isPrevPos) { int op = eff.value >> 8; int val = eff.value & 0x00ff; if (0 < op && op < 5 && -1 < val && val < 128) opnaCtrl_->setTLControlFM(ch, op - 1, val); } } break; case EffectType::MLControl: if (!isSetMLCtrlFM[uch]) { isSetMLCtrlFM[uch] = true; if (isPrevPos) { int op = eff.value >> 4; int val = eff.value & 0x0f; if (0 < op && op < 5 && -1 < val && val < 16) opnaCtrl_->setMLControlFM(ch, op - 1, val); } } break; case EffectType::ARControl: if (!isSetARCtrlFM[uch]) { isSetARCtrlFM[uch] = true; if (isPrevPos) { int op = eff.value >> 8; int val = eff.value & 0x00ff; if (0 < op && op < 5 && -1 < val && val < 32) opnaCtrl_->setARControlFM(ch, op - 1, val); } } break; case EffectType::DRControl: if (!isSetDRCtrlFM[uch]) { isSetDRCtrlFM[uch] = true; if (isPrevPos) { int op = eff.value >> 8; int val = eff.value & 0x00ff; if (0 < op && op < 5 && -1 < val && val < 32) opnaCtrl_->setDRControlFM(ch, op - 1, val); } } break; case EffectType::RRControl: if (!isSetRRCtrlFM[uch]) { isSetRRCtrlFM[uch] = true; if (isPrevPos) { int op = eff.value >> 4; int val = eff.value & 0x0f; if (0 < op && op < 5 && -1 < val && val < 16) opnaCtrl_->setRRControlFM(ch, op - 1, val); } } break; case EffectType::Brightness: if (!isSetBrightFM[uch]) { isSetBrightFM[uch] = true; if (isPrevPos) { if (0 < eff.value) opnaCtrl_->setBrightnessFM(ch, eff.value - 0x80); } } break; case EffectType::XVolumeSlide: if (!isSetXVolSldFM[uch]) { isSetXVolSldFM[uch] = true; if (isPrevPos) { int factor = (eff.value >> 4) - (eff.value & 0x0f); opnaCtrl_->setXVolumeSlideFM(ch, factor); } } break; default: break; } } break; } case SoundSource::SSG: { // Volume int vol = step.getVolume(); if (!isSetVolSSG[uch] && step.hasVolume() && vol < bt_defs::NSTEP_SSG_VOLUME) { isSetVolSSG[uch] = true; if (isPrevPos) opnaCtrl_->setVolumeSSG(ch, vol); } // Instrument if (!isSetInstSSG[uch] && step.hasInstrument()) { if (auto inst = std::dynamic_pointer_cast( instMan_.lock()->getInstrumentSharedPtr(step.getInstrumentNumber()))) { isSetInstSSG[uch] = true; if (isPrevPos) opnaCtrl_->setInstrumentSSG(ch, inst); } } // Effects for (int i = Step::N_EFFECT - 1; i > -1; --i) { Effect eff = effect_utils::validateEffect(SoundSource::SSG, step.getEffect(i)); switch (eff.type) { case EffectType::Arpeggio: if (!isSetArpSSG[uch]) { isSetArpSSG[uch] = true; if (isPrevPos) opnaCtrl_->setArpeggioEffectSSG(ch, eff.value >> 4, eff.value & 0x0f); } break; case EffectType::PortamentoUp: if (!isSetPrtSSG[uch]) { isSetPrtSSG[uch] = true; if (isPrevPos) opnaCtrl_->setPortamentoEffectSSG(ch, eff.value); } break; case EffectType::PortamentoDown: if (!isSetPrtSSG[uch]) { isSetPrtSSG[uch] = true; if (isPrevPos) opnaCtrl_->setPortamentoEffectSSG(ch, -eff.value); } break; case EffectType::TonePortamento: if (!isSetPrtSSG[uch]) { isSetPrtSSG[uch] = true; if (isPrevPos) opnaCtrl_->setPortamentoEffectSSG(ch, eff.value, true); } break; case EffectType::Vibrato: if (!isSetVibSSG[uch]) { isSetVibSSG[uch] = true; if (isPrevPos) opnaCtrl_->setVibratoEffectSSG(ch, eff.value >> 4, eff.value & 0x0f); } break; case EffectType::Tremolo: if (!isSetTreSSG[uch]) { isSetTreSSG[uch] = true; if (isPrevPos) opnaCtrl_->setTremoloEffectSSG(ch, eff.value >> 4, eff.value & 0x0f); } break; case EffectType::VolumeSlide: if (!isSetVolSldSSG[uch]) { isSetVolSldSSG[uch] = true; if (isPrevPos) { int hi = eff.value >> 4; int low = eff.value & 0x0f; if (hi && !low) opnaCtrl_->setVolumeSlideSSG(ch, hi, true); // Slide up else if (!hi) opnaCtrl_->setVolumeSlideSSG(ch, low, false); // Slide down } } break; case EffectType::SpeedTempoChange: if (!(speedStates & 0x4)) { if (eff.value < 0x20) { // Speed change if (!(speedStates & 0x1)) { speedStates |= 0x1; if (isPrevPos) effSpeedChange(eff.value); } } else if (!(speedStates & 0x2)) { // Tempo change speedStates |= 0x2; if (isPrevPos) effTempoChange(eff.value); } } break; case EffectType::Groove: if (eff.value < static_cast(mod_.lock()->getGrooveCount()) && !speedStates) { speedStates |= 0x4; if (isPrevPos) effGrooveChange(eff.value); } break; case EffectType::Detune: if (!isSetDtnSSG[uch]) { isSetDtnSSG[uch] = true; if (isPrevPos) opnaCtrl_->setDetuneSSG(ch, eff.value - 0x80); } break; case EffectType::FineDetune: if (!isSetFiDtnSSG[uch]) { isSetFiDtnSSG[uch] = true; if (isPrevPos) opnaCtrl_->setFineDetuneSSG(ch, eff.value - 0x80); } break; case EffectType::ToneNoiseMix: if (-1 < eff.value && eff.value < 4 && !isSetTNMixSSG[uch]) { isSetTNMixSSG[uch] = true; if (isPrevPos) opnaCtrl_->setToneNoiseMixSSG(ch, eff.value); } break; case EffectType::NoisePitch: if (-1 < eff.value && eff.value < 32 && !isSetNoisePitchSSG) { isSetNoisePitchSSG = true; if (isPrevPos) opnaCtrl_->setNoisePitchSSG(ch, eff.value); } break; case EffectType::HardEnvHighPeriod: if (!isSetHardEnvPeriodHighSSG) { isSetHardEnvPeriodHighSSG = true; if (isPrevPos) opnaCtrl_->setHardEnvelopePeriod(ch, true, eff.value); } break; case EffectType::HardEnvLowPeriod: if (!isSetHardEnvPeriodLowSSG) { isSetHardEnvPeriodLowSSG = true; if (isPrevPos) opnaCtrl_->setHardEnvelopePeriod(ch, false, eff.value); } break; case EffectType::AutoEnvelope: if (!isSetAutoEnvSSG) { isSetAutoEnvSSG = true; if (isPrevPos) opnaCtrl_->setAutoEnvelopeSSG(ch, (eff.value >> 4) - 8, eff.value & 0x0f); } break; case EffectType::XVolumeSlide: if (!isSetXVolSldSSG[uch]) { isSetXVolSldSSG[uch] = true; if (isPrevPos) { int factor = (eff.value >> 4) - (eff.value & 0x0f); opnaCtrl_->setXVolumeSlideSSG(ch, factor); } } break; default: break; } } break; } case SoundSource::RHYTHM: { // Volume int vol = step.getVolume(); if (!isSetVolRhythm[uch] && step.hasVolume() && vol < bt_defs::NSTEP_RHYTHM_VOLUME) { isSetVolRhythm[uch] = true; if (isPrevPos) opnaCtrl_->setVolumeRhythm(ch, vol); } // Effects for (int i = Step::N_EFFECT - 1; i > -1; --i) { Effect eff = effect_utils::validateEffect(SoundSource::RHYTHM, step.getEffect(i)); switch (eff.type) { case EffectType::Pan: if (-1 < eff.value && eff.value < 4 && !isSetPanRhythm[uch]) { isSetPanRhythm[uch] = true; if (isPrevPos) opnaCtrl_->setPanRhythm(ch, eff.value); } break; case EffectType::SpeedTempoChange: if (!(speedStates & 0x4)) { if (eff.value < 0x20) { // Speed change if (!(speedStates & 0x1)) { speedStates |= 0x1; if (isPrevPos) effSpeedChange(eff.value); } } else if (!(speedStates & 0x2)) { // Tempo change speedStates |= 0x2; if (isPrevPos) effTempoChange(eff.value); } } break; case EffectType::Groove: if (eff.value < static_cast(mod_.lock()->getGrooveCount()) && !speedStates) { speedStates |= 0x4; if (isPrevPos) effGrooveChange(eff.value); } break; case EffectType::MasterVolume: if (-1 < eff.value && eff.value < 64 && !isSetMVolRhythm) { isSetMVolRhythm = true; if (isPrevPos) opnaCtrl_->setMasterVolumeRhythm(eff.value); } break; default: break; } } break; } case SoundSource::ADPCM: { // Volume int vol = step.getVolume(); if (!isSetVolADPCM && step.hasVolume() && vol < bt_defs::NSTEP_ADPCM_VOLUME) { isSetVolADPCM = true; if (isPrevPos) opnaCtrl_->setVolumeADPCM(vol); } // Instrument if (!isSetInstADPCM && step.hasInstrument()) { if (auto inst = std::dynamic_pointer_cast( instMan_.lock()->getInstrumentSharedPtr(step.getInstrumentNumber()))) { isSetInstADPCM = true; if (isPrevPos) opnaCtrl_->setInstrumentADPCM(inst); } } // Effects for (int i = Step::N_EFFECT - 1; i > -1; --i) { Effect eff = effect_utils::validateEffect(SoundSource::ADPCM, step.getEffect(i)); switch (eff.type) { case EffectType::Arpeggio: if (!isSetArpADPCM) { isSetArpADPCM = true; if (isPrevPos) opnaCtrl_->setArpeggioEffectADPCM(eff.value >> 4, eff.value & 0x0f); } break; case EffectType::PortamentoUp: if (!isSetPrtADPCM) { isSetPrtADPCM = true; if (isPrevPos) opnaCtrl_->setPortamentoEffectADPCM(eff.value); } break; case EffectType::PortamentoDown: if (!isSetPrtADPCM) { isSetPrtADPCM = true; if (isPrevPos) opnaCtrl_->setPortamentoEffectADPCM(-eff.value); } break; case EffectType::TonePortamento: if (!isSetPrtADPCM) { isSetPrtADPCM = true; if (isPrevPos) opnaCtrl_->setPortamentoEffectADPCM(eff.value, true); } break; case EffectType::Vibrato: if (!isSetVibADPCM) { isSetVibADPCM = true; if (isPrevPos) opnaCtrl_->setVibratoEffectADPCM(eff.value >> 4, eff.value & 0x0f); } break; case EffectType::Tremolo: if (!isSetTreADPCM) { isSetTreADPCM = true; if (isPrevPos) opnaCtrl_->setTremoloEffectADPCM(eff.value >> 4, eff.value & 0x0f); } break; case EffectType::Pan: if (-1 < eff.value && eff.value < 4 && !isSetPanADPCM) { isSetPanADPCM = true; if (isPrevPos) opnaCtrl_->setPanADPCM(eff.value); } break; case EffectType::VolumeSlide: if (!isSetVolSldADPCM) { isSetVolSldADPCM = true; if (isPrevPos) { int hi = eff.value >> 4; int low = eff.value & 0x0f; if (hi && !low) opnaCtrl_->setVolumeSlideADPCM(hi, true); // Slide up else if (!hi) opnaCtrl_->setVolumeSlideADPCM(low, false); // Slide down } } break; case EffectType::SpeedTempoChange: if (!(speedStates & 0x4)) { if (eff.value < 0x20) { // Speed change if (!(speedStates & 0x1)) { speedStates |= 0x1; if (isPrevPos) effSpeedChange(eff.value); } } else if (!(speedStates & 0x2)) { // Tempo change speedStates |= 0x2; if (isPrevPos) effTempoChange(eff.value); } } break; case EffectType::Groove: if (eff.value < static_cast(mod_.lock()->getGrooveCount()) && !speedStates) { speedStates |= 0x4; if (isPrevPos) effGrooveChange(eff.value); } break; case EffectType::Detune: if (!isSetDtnADPCM) { isSetDtnADPCM = true; if (isPrevPos) opnaCtrl_->setDetuneADPCM(eff.value - 0x80); } break; case EffectType::FineDetune: if (!isSetFiDtnADPCM) { isSetFiDtnADPCM = true; if (isPrevPos) opnaCtrl_->setFineDetuneADPCM(eff.value - 0x80); } break; case EffectType::XVolumeSlide: if (!isSetXVolSldADPCM) { isSetXVolSldADPCM = true; if (isPrevPos) { int factor = (eff.value >> 4) - (eff.value & 0x0f); opnaCtrl_->setXVolumeSlideADPCM(factor); } } break; default: break; } } break; } } } // Move position isPrevPos = true; if (--pos.step < 0) { if (--pos.order < 0) break; pos.step = static_cast(getPatternSizeFromOrderNumber(curSongNum_, pos.order)) - 1; } } // Sequence reset for (size_t ch = 0; ch < fmch; ++ch) { opnaCtrl_->haltSequencesFM(static_cast(ch)); } for (size_t ch = 0; ch < 3; ++ch) { opnaCtrl_->haltSequencesSSG(static_cast(ch)); } opnaCtrl_->haltSequencesADPCM(); } size_t PlaybackManager::getOrderSize(int songNum) const { return mod_.lock()->getSong(songNum).getOrderSize(); } size_t PlaybackManager::getPatternSizeFromOrderNumber(int songNum, int orderNum) const { return mod_.lock()->getSong(songNum).getPatternSizeFromOrderNumber(orderNum); } BambooTracker-0.6.5/BambooTracker/playback.hpp000066400000000000000000000173021476276175200212730ustar00rootroot00000000000000/* * Copyright (C) 2019-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include #include "module.hpp" #include "effect.hpp" #include "enum_hash.hpp" #include "bamboo_tracker_defs.hpp" class OPNAController; class InstrumentsManager; class TickCounter; class EffectMemory { public: EffectMemory(); void enqueue(const Effect& eff); void clear(); using container_type = std::vector; using reference = container_type::reference; using const_reference = container_type::const_reference; using iterator = container_type::iterator; using const_iterator = container_type::const_iterator; using value_type = container_type::value_type; iterator begin() noexcept { return mem_.begin(); } const_iterator begin() const noexcept { return mem_.begin(); } const_iterator cbegin() const noexcept { return mem_.cbegin(); } iterator end() noexcept { return mem_.end(); } const_iterator end() const noexcept { return mem_.end(); } const_iterator cend() const noexcept { return mem_.cend(); } iterator erase(iterator position) { return mem_.erase(position); } iterator erase(const_iterator position) { return mem_.erase(position); } private: std::vector mem_; }; class PlaybackManager { public: PlaybackManager(std::shared_ptr opnaCtrl, std::weak_ptr instMan, std::weak_ptr tickCounter, std::weak_ptr mod, bool isRetrieveChannel); void setSong(std::weak_ptr mod, int songNum); // Play song void startPlaySong(int order); void startPlayFromStart(); void startPlayPattern(int order); void startPlayFromPosition(int order, int step); void playStep(int order, int step); void stopPlaySong(); bool isPlaySong() const noexcept; bool isPlayingStep() const noexcept; int getPlayingOrderNumber() const noexcept; int getPlayingStepNumber() const noexcept; // Stream events /// 0<: Tick /// 0: Step /// -1: Stop int streamCountUp(); void checkPlayPosition(int maxStepSize); void setChannelRetrieving(bool enabled); private: std::shared_ptr opnaCtrl_; std::weak_ptr instMan_; std::weak_ptr tickCounter_; std::weak_ptr mod_; std::mutex mutex_; int curSongNum_; SongStyle songStyle_; struct Position { int order, step; static constexpr int INVALID = -1; Position(int o, int s) : order(o), step(s) {} void set(int o, int s) // Faster than making and copying a new instance { order = o; step = s; } void invalidate() noexcept { set(INVALID, INVALID); } bool isValid() const noexcept { return (order > INVALID && step > INVALID); } } playingPos_, nextReadPos_; uint8_t playStateFlags_; // Play song bool isFindNextStep_; void startPlay(); void stopPlay(); bool stepDown(); void findNextStep(); void checkValidPosition(); void stepProcess(); void executeFMStepEvents(const Step& step, int ch, bool calledByNoteDelay = false); void executeSSGStepEvents(const Step& step, int ch, bool calledByNoteDelay = false); void executeRhythmStepEvents(const Step& step, int ch, bool calledByNoteDelay = false); void executeADPCMStepEvents(const Step& step, bool calledByNoteDelay = false); EffectMemory playbackSpeedEffMem_, posChangeEffMem_; std::unordered_map> effOnKeyOnMem_, effOnStepBeginMem_; struct RegisterUnit { int address, value; bool hasCompleted; }; using DirectRegisterSetQueue = std::vector; using DirectRegisterSetSource = std::vector; std::unordered_map directRegisterSets_; bool executeStoredEffectsGlobal(); void storeEffectToMapFM(int ch, const Effect& eff); void executeStoredEffectsFM(int ch); void storeEffectToMapSSG(int ch, const Effect& eff); void executeStoredEffectsSSG(int ch); void storeEffectToMapRhythm(int ch, const Effect& eff); void executeStoredEffectsRhythm(int ch); void storeEffectToMapADPCM(int ch, const Effect& eff); void executeStoredEffectsADPCM(); void storeDirectRegisterSetEffectToQueue(SoundSource src, int ch, const Effect& eff); void executeDirectRegisterSetEffect(DirectRegisterSetQueue& queue); bool effPositionJump(int nextOrder); void effSongEnd(); bool effPatternBreak(int nextStep); void effSpeedChange(int speed); void effTempoChange(int tempo); void effGrooveChange(int num); void tickProcess(int rest); /** * @brief checkFMDelayEventsInTick * @param step * @param ch * @return \c true \c if note delay is occurred. */ bool checkFMDelayEventsInTick(const Step& step, int ch); /** * @brief checkFMNoteDelayAndEnvelopeReset * @param step * @param ch * @return \c true \c if note delay is occurred. */ bool checkFMNoteDelayAndEnvelopeReset(const Step& step, int ch); void envelopeResetEffectFM(const Step& step, int ch); /** * @brief checkSSGDelayEventsInTick * @param step * @param ch * @return \c true \c if note delay is occurred. */ bool checkSSGDelayEventsInTick(const Step& step, int ch); /** * @brief checkRhythmDelayEventsInTick * @param step * @param ch * @return \c true \c if note delay is occurred. */ bool checkRhythmDelayEventsInTick(const Step& step, int ch); /** * @brief checkADPCMDelayEventsInTick * @param step * @return \c true \c if note delay is occurred. */ bool checkADPCMDelayEventsInTick(const Step& step); std::vector ntDlyCntFM_, ntReleaseDlyCntFM_, volDlyCntFM_, ntCutDlyCntFM_, rtrgCntFM_; std::vector ntDlyCntSSG_, ntReleaseDlyCntSSG_, volDlyCntSSG_, ntCutDlyCntSSG_, rtrgCntSSG_; std::vector ntDlyCntRhythm_, ntReleaseDlyCntRhythm_, volDlyCntRhythm_, ntCutDlyCntRhythm_, rtrgCntRhythm_; int ntDlyCntADPCM_, ntReleaseDlyCntADPCM_, volDlyCntADPCM_, ntCutDlyCntADPCM_, rtrgCntADPCM_; std::vector volDlyValueFM_, volDlyValueSSG_, volDlyValueRhythm_; int volDlyValueADPCM_; std::vector tposeDlyCntFM_, tposeDlyCntSSG_; int tposeDlyCntADPCM_; std::vector tposeDlyValueFM_, tposeDlyValueSSG_; int tposeDlyValueADPCM_; std::vector rtrgCntValueFM_, rtrgCntValueSSG_, rtrgCntValueRhythm_; int rtrgCntValueADPCM_; std::vector rtrgVolValueFM_, rtrgVolValueSSG_, rtrgVolValueRhythm_; int rtrgVolValueADPCM_; void clearEffectMaps(); void clearDelayWithinStepCounts(); void clearDelayBeyondStepCounts(); void clearFMDelayBeyondStepCounts(int ch); void clearSSGDelayBeyondStepCounts(int ch); void clearRhythmDelayBeyondStepCounts(int ch); void clearADPCMDelayBeyondStepCounts(); void updateDelayEventCounts(); bool isRetrieveChannel_; void retrieveChannelStates(); size_t getOrderSize(int songNum) const; size_t getPatternSizeFromOrderNumber(int songNum, int orderNum) const; }; BambooTracker-0.6.5/BambooTracker/precise_timer.cpp000066400000000000000000000032371476276175200223340ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "precise_timer.hpp" #include PreciseTimer::~PreciseTimer() { stop(); } void PreciseTimer::setFunction(std::function func) { func_ = func; } void PreciseTimer::setInterval(const int microsec) { time_.store(microsec); } void PreciseTimer::start() { isContinue_.store(true); thread_ = std::thread([&] { while (isContinue_.load()) { std::this_thread::sleep_for(std::chrono::microseconds(time_.load())); func_(); } }); } void PreciseTimer::stop() { if (isContinue_.load()) { isContinue_.store(false); thread_.join(); } } BambooTracker-0.6.5/BambooTracker/precise_timer.hpp000066400000000000000000000027131476276175200223370ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include class PreciseTimer { public: ~PreciseTimer(); void setFunction(std::function func); void setInterval(const int microsec); void start(); void stop(); private: std::atomic_int time_; std::function func_; std::thread thread_; std::atomic_bool isContinue_; }; BambooTracker-0.6.5/BambooTracker/resources/000077500000000000000000000000001476276175200210035ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/resources/doc/000077500000000000000000000000001476276175200215505ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/resources/doc/doc.qrc000066400000000000000000000001631476276175200230240ustar00rootroot00000000000000 keyboard_shortcuts.html BambooTracker-0.6.5/BambooTracker/resources/doc/keyboard_shortcuts.html000066400000000000000000000076251476276175200263660ustar00rootroot00000000000000 Keyboard shortcuts

General

Key Command
Ctrl+N Create new module
Ctrl+O Open module
Ctrl+S Save module
Ctrl+P Open module property dialog
Ctrl+I Open current instrument editor
Return Play/stop song
F1 Show effect list dialog
F5 Play from start
F6 Play pattern
F7 Play from current position
F8 Stop song
Space Toggle jam/edit mode
Alt+L Focus on instrument list
Alt+O Focus on order list
Alt+P Focus on pattern editor
F12 Kill sound

Instrument list

Key Command
Insert Add instrument
Delete Remove instrument
Ctrl+I Open current instrument editor

Order list

Key Command
Ctrl+C Copy
Ctrl+V Paste
Ctrl+A Select track/all
Ctrl+D Duplicate order
Alt+D Clone order
Home Jump to first order
End Jump to last order
PageUp Jump to upper oder
PageDown Jump to lower oder
Insert Insert order below
Delete Delete order
Escape Deselect

Pattern editor

Key Command
Ctrl+C Copy
Ctrl+X Cut
Ctrl+V Paste
Ctrl+M Paste and mix
Ctrl+A Select track/all
Ctrl+G Interpolate
Ctrl+R Reverse
Ctrl+F1 Decrease note
Ctrl+F2 Increase note
Ctrl+F3 Decrease octave
Ctrl+F4 Increase octave
Alt+F9 Toggle track
Alt+F10 Solo track
Alt+S Replace instrument
Tab Jump to right track
BackTab Jump to left track
Home Jump to first step
End Jump to last step
PageUp Jump to upper step
PageDown Jump to lower step
Ctrl+Up Jump to upper 1st highlighted step
Ctrl+Down Jump to lower 1st highlighted step
Insert Insert step
BackSpace Delete the step above
Delete Delete commands
Escape Deselection
- Key off
* (numpad) Increase octave/echo buffer number
/ (numpad) Decrease octave/echo buffer number
^ Echo buffer access
BambooTracker-0.6.5/BambooTracker/resources/icon/000077500000000000000000000000001476276175200217335ustar00rootroot00000000000000BambooTracker-0.6.5/BambooTracker/resources/icon/BambooTracker.icns000066400000000000000000000206371476276175200253340ustar00rootroot00000000000000icns!info bplist00X$versionX$objectsY$archiverT$topU$null WNS.keysZNS.objectsV$class TnameTiconZ$classnameX$classes\NSDictionaryXNSObject_NSKeyedArchiverTroot#-27=CJR]dfhjlnsx}ic05 'ARGB3333̀3f3f3f3f3f3f3f33̅333݇333f333f33 3f̀3f3f3f3f3f33ff3̃33ff33ff33ff3 3f3ff3f3f3f33ff3ww"3w"3ww""wwww3w""ww"wwww3""ww"ww3"ww3"3f3"w"""3ww"3w"ww""ww3"ww"ww"ww3"ww"ww"ww3"ww"ww3"3f3""3ww"w"ww"ww""ww"ww"ww"ww"ww"ww"ww"ww "ww"ww"ww"ww3""""3ww"ww"ww"ww""ww"ww"ww"ww"ww"ww"ww"ww""ww"ww"ww"ww""""̅3333̀3f3f3f3f3f3f3f33̅333U333U̙"333U̙"33 3U̙"̀3f3f3f3f3f33"U"3ݙ33݀ݙ3f333f3f333f3f33 3f33ff3f3f3f33ff3f3̙3ff3̙fffff3̙fff3ffff3ff3f3ffff3f3ff3fff3fff3ff3ff3f3ffff3fffffffffff ffff3ffffffff3ffffffffffff#ffff̙̙f̙f̙f̙f̅3333̀3f3f3f3f3f3f3f33̅333U333UDD333UDD33 3UDD̀3f3f3f3f3f33DUD3U"33UU""UU33"UU"UU33"UU"UU3 3"UU3"3f3f3f33"3UUDDD3UDD3UUDDDUUDDDUU3UDUUDDDUUDUUD3DDDUUDDDUUD3DD UUDD3D3f3DUD3UUDD3UDUUDDDUUDD3DUUDUUDDDUUDD3DUUDUUDDDUUDD3DUUD UUDD3D3f3D3UUDDUDUUDDUUDDDUUDUUDUUDUUDDDUUDUUDUUDUUDDDUUDUUDUUD UUDD3D3UUDDUUDDUUDDUUDDDUUDUUDUUDUUDDDUUDUUDUUDUUDDDUUDUUDUUD UUDD̙Dic08 PNG  IHDRkXTPLTE333fDf3fDfffD"DwU"DfUU̙"U̻^ @IDATxْ8Мg2.BAN JKPRUz)yS\Y 1)y3_Y 1)y3eGRe\iR n0W ;wO&hSw R^1(T?Y"fq5>^_l{oM@O>}1=g4 W& ?SM b_W_g;^0=b=@)pc٧4cic7<)x~ c?~ /4v`~~@ U80oٿ|(S?)}'2&p:?d0  0 Fʾ rsKG׸L˳UӸ^ѯ =(&@&a-  1PT3ufhy 0OUyV8*o[ 4@hy 0OUyV8*o[ 4@hy 0OUy֧/%H GG?"Jɯ p&`7F6"7oH0#`1J))?epc ':!&1'oBI˸~6?+I;=QGE?ڧu$~Wa?I;]#nڹ;㇝L;]=D"A}.@ ݬM^0wAӏ{gx0i#`P88뫣>.N^.^.嫅-2lA @# 0"Rʬ5@}0&$K'/z tG @|)8y'ūQ0yp0@8Ίh~]zeӰZB& 1`LIi3i$xr⋾ZB&#;0yF̸In_-  "ΓQU]-u''@ 2dƭND5^8~#C2GюO9B}oFI]> lI =>A:jȟ_UV[͛6p V _͋}±TO@OՉ[͋A±T>}~0zV$Vu[X}{@=g5/nEUV8&'`uVbp,G`0Jj^CUzzn Nj^ bU> _@r/D7LIIɟ_t \S 4d[§i8j諅O+p >oW W }&ޢ>%N)@VLE_-|J^SxZ8 Y3})ypNg-jS 4d[§i8j諅O+p >oW W }&oB 4ߟH'ťOÇ!M&0dΏQ]\J~${œR'ᄻ'\2J(..%x=Ivq)œp `.cd_<ɞt~$I8 |'q̲K[t&:C_)8  ? K[t&:C_)8  ? K[t&:C_)8  ? K[t&:C_)8 *oZ%ޢd8 OKE?jpV x~ > 4>%/ }iX-|J^-)@V,ӰZ[SYa)yYNjSoOg§%ޢd8 OKE?jpV x~ > 4>%/ } ?i!R򓟚'yBS|O8&$FGO~jdO&H;H~N|O8ɞL%eVȫy+c X{&h l{oU[mό;W}qHN{Y[83 |v3!}g}Iۙ{g/>*?3ʸ(}[fMO6[NR\]1w3ۀ]k212>{u]@W+pH;nG9tfhmAḠ[N`w|YyOY}  :N ~BHSq.*@ό+tP7xf\ ` ,3 ]+Td1 WX$)n9vXvO5>$y`b! T ^ `̸B'n ,f+dFN YVȌ+tbBf\7xH32 +@W ^ `̸B'n ,f[gXCGǤx 1h@cv6IENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/BambooTracker.ico000066400000000000000000003066561476276175200251620ustar00rootroot00000000000000 -V (N.@@ (Bv6  x hFPNG  IHDR\rfsRGBgAMA a pHYsod-IDATx^эfrA.ȅr] ¸ dK. r -d܍$`\pS}e/iM&TZse*㗡DJknL57QҚ?~jiM&TZse*㗡DJknL57QҚ?~jiM&TZse*㗡DJknL57QҚ?~jiM&TZse*㗡DJknL57QҚϟ?5=Ǐ5'giIYZsz֜5'giIYZЛ,9I=KkNRҚԳ$,9I=Kkz5'giIYZsz֜5'giBoг$,9I=KkNRҚԳ$,YMz֜5'giIYZsz֜5 @ҚԳ$,9I=KkNRҚԳf7]?YZsz֜5'giIYZsz,=KkNRҚԳ$,9I=KkNRҚthh57QҚ(S?럿?/i&#~2tiJknL57QңLK4?~457QҚ(S}&zG_Ɉ ]Қ(SiM>=ң/dď_.MiM&Tzt}їf2/C&TZse*=DK3㗡KSZse*2gG_zХ)2DJ3ѣ/=LFeҔDJknLGSztGP/ۯi&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&?+I?~%9I=KkNRҚԳ$,YQ~NRң^(sMz֜5'giIYZУ+=ړԳ\z5'giIYZsz,J$,=2t}giIYZsz֜5 =ң=I=Kz̥7]?YZsz֜5'giBhORң^(sMz֜5'giIYZУ+=ړԳ\z5'giIYZsz,J$,=2t}giIYZsz֜5 =ң=I=Kz̥7]?럟ڿ_%e.EiMt;sz\o5L4KoGQZse*yΜ;Wz5L4KoGQZse*yΜ;Wz5L4KoGQZse*yΜ;Wz5L4KoGQZse*yΜ;Wz5L4KoGQZse*yΜ;Wz5L4KoGQZse*yΜ;Wz5L4KoGQZse*yΜ;Wz5m4jֿ~4RQDJwtgJwn;Y5 4jZj)M(JknL;Y3;Pҝ,LFZ ]B5KC- &z57Qҝ,ݙҝ[fNdi&#~|MC-. mZ|=Қ(SNL-Tt'Kw4?BPPKC-eEiMt'KwtY;YɈ_PKKC[fi2D&T;Ss ,ҝ,dďiХ-T4PKoGQZse*ҝ)ݹjdNf24RYji7ѣ(2dΔB5Kwt'K3kj)tih , 4RQDJwtgJwn;Y5 4jZj)M(JknL;Y3;Pҝ,LFZ ]B5KC- &z57Qҝ,ݙҝ[fNdi&#~|MC-. mZ|=Қ(SNL-Tt'Kw4?BPPKC-eEiMt'KwtY;YɈ_PKKC[fi2D&T;Ss ,ҝ,dďiХ-T4PKoGQZse*ҝ)ݹjdNf24RYji7ѣ(2dΔB5Kwt'K3kj)tih , 4RQDJwtgJwn;Y5 4jZj)M(JknL;Y3;Pҝ,LFZ ]B5KC- &z57Qҝ,ݙҝ[fNdi&#~|MC-. mZ|=Қ(SNL-Tt'Kw4?BPPKC-eEiMt'KwtY;YɈ_PKKC[fi2D&T;Ss ,ҝ,dďiХ-T4PKoGQZse*ҝ)ݹjdNf24RYji7ѣ(2dΔB5Kwt'K3_PK. $, m'㗴$,9I=Kk;uzBKo>'җ֜5'giBwtNRҝ[(sM$]ҚԳ$,YNIYs e.2K_Zsz֜5 ݉ҝ:I=Kwn̥7]?PtKkNRҚԳf;QS'g-|.}iIYZsz,t'Jw$,ݹ2t}|@Oҥ/9I=KkNRҚDN;Pқ(I5'giIYZН(ݩԳt\zcKk|}~Ak5'LJgRI)SgN̥;Y_SPNRPKC-e>IfGQZsΤt&2zIY\5* $, 4RtKkz5'LJgRI)SgN̥;Y_SPNRPKC-e>IfGQZsΤt&2zIY\5* $, 4RtKkz5'LJgRI)SgN̥;Y_SPNRPKC-e>IfGQZsΤt&2zIY\5* $, 4RtKkz5'LJgRI)SgN̥;Y_SPNRPKC-e>IfGQZsΤt&2zIY\5m4j.Z\B=z5=μ4|=2B5K{~PЙ5 4jZj)sfhO \ї֔T:L0jTTs ,ACg64RYjaŚ=-sG_ZSSK3y'S)S-Teو_PKKC[fq2kPυ}iMiO3/3fhOL;PҞ9tf#~|MC-. mšZ\B=z5=μ4|=2B5K{~PЙ5 4jZj)sfhO \ї֔T:L0jTTs ,ACg64RYjaŚ=-sG_ZSSK3y'S)S-Teو_PKKC[fq2kPυ}iMiO3/3fhOL;PҞ9tf#~|MC-. mš5 |G_:R$yqOL3)\fiIGZ ]B5C- AkB=BtfI:BJgRPҞ0ҙ5 4jZքz.34}Jt=2Τs=?`&3kj)tih ,0 \gh =ҙ2?`'̋{ e*IB5K{~LJg>4RYjaZ`z3+e~OҙT:Rυj|ďiХ-T8P&s/KgV=3/)t& ,3)_PKKC[fq>hM=C3_їά{>Ig^S(SLJ=YfR:?BPP C}КP{fУ/Y)|μPҙz.T̤t#~|MC-. mš;i'gRBg =KwtgJ{Zf1S!SGZ ]B5C- wLOң/Τl=zdΔPbBҙ5 4jZ酪G_Ia ق{*,ҝ)iLL3kj)tih ,0I3=I8ž:TY;SB5 Jg>4RYjafz}q&=/tf гt'Kwj32|ďiХ-T8P'$=L {^SgNLiO ,f*d*_PKKC[fqNIzřЙ-Bҝ,ݙҞYTT:?BPP C4ӓ3)y3[pO;Y3=-Tt擟?+4M~z>`h e>Ǐ5'iK[ Dw"$|О:қ(Iz5'iG_z Dw"$|О:қ(Iz5'iG_z Dw"$|О:қ(Iz5'iG_z Dw"$|О:қ(Iz5'iG_z Dw"$|О:қ(Iz5'iG_z Dw"$|О:қ(Iz5'iG_z Dw"$|О:қ(Iz5'iG_z Dw"$|О:қ?FKqz4RRfLKgZZSyz\3k UIYji7ѣ/3љδ$,t'Kg>4Գ4PKoG_g3-iiMIYsN|ď)Ti('gi2D4DgZ:ҚRϓԳҝ,_SPNRPKC-e}iδt5'giϥ;Y:?PZ|=L?iLKkJ=ORҞKwt#~|MJC9I=KC- &z~&:ҙ֔z=dGBrzZj)MK3Lt3-)I$,4Ig2ARϓA{LKo>'ї֜ęf;L&Tyz>hiM$=Қ8'J=ORy3-2G_Zsg3 SI=t7]?PKkNB3t&d*h $|Оz7]?QОNRy=B5C{:I=dMGT3A{^fhO'PОNRy7Yz $|ОI=/T3A{Mt}|D5C{:I=jtz>h $|Оz7]?QОNRy=B5C{:I=dM#sC#NRҡZYIYTTYIYTTYIYTTz#~|MJC9I=KC- TԳTԳTԳ&GBrzZjfiO'g)S)SfiO'g)S)SfiO'g)S)SM5* $, 4RҞNRRRRҞNRRRRҞNRRRқk UIYji=LL=LL=LL794Գ4PK5K{:I=KJJ5K{:I=KJJ5K{:I=KJJorď)Ti('gijtz22jtz22jtz22_SPNRPKC-,$,e*e*,$,e*e*,$,e*e*?MBP?_ZTYB5 JJ=K{^f1S!S)Sgi ,f*d*?BPP C-e*,yLLL=/TԳj32_PKKC[fq2zPbBRRҞYTTTYB5 JorďiХ-T8PKJ=K{^f1S!S)Sgi ,f*d*e*,yLL794RYjaL=/TԳj322zPbBқkj)tih ,0RRҞYTTTYB5 JJ=K{^f1S!SM5 4jZj)Sgi ,f*d*e*,yLLL=/T&GZ ]B5C- Գj322zPbBRRҞYTTz#~|MC-. mšZTYB5 JJ=K{^f1S!S)Sgi ,f*d*?BPP C-e*,yLLL=/TԳj32_PKKC[fq2zPbBRRҞYTTTYB5 JorďiХ-T8PKJ=K{^f1S!S)Sgi ,f*d*e*,yLL794RYjaL=/TԳj322zPbBқkj)tih ,0RRҞYTTTYB5 JJ=K{^f1S!SM5 4jZj)Sgi ,f*d*e*,yLLL=/T&GZ ]B5C- Գj322zPbBRRҞYTTz#~|MC-. mšZTYB5 JJ=K{^f1S!S)Sgi ,f*d*?BPP C-e*,yLLL=/TԳj32_PKKC[fq2zPbBRRҞYTTTYB5 JorďiХ-T8PKJ=K{^f1S!S)Sgi ,f*d*e*,yLL794RYjaL=/TԳj322zPbBқkj)tih ,0RRҞYTTTYB5 JJ=K{^f1S!SM5 4jZj)Sgi ,f*d*e*,yLLL=/T&GZ ]B5C- Գj322zPbBRRҞYTTz?PJk0sL5I9PҚ$(Si̓t#9PҚ$(Si̓tzA{:Hor(Si̓tzA{:I=TZ=7zA{:I=TZ=e*yОқ\||D=TZ=e*yОNRρ2e*yОNRρ2 if_arrow_redo_4989.png if_arrow_undo_4998.png if_cog_5175.png if_cut_red_5249.png if_disk_5276.png if_folder_5362.png if_page_white_5568.png if_page_white_copy_5579.png if_paste_plain_5629.png if_resultset_last_5687.png if_resultset_next_5688.png if_shape_square_5750.png if_table_edit_5800.png inst_ssg.png inst_fm.png BambooTracker.ico iconfinder_add_4941.png iconfinder_cross_5233.png iconfinder_page_copy_5551.png iconfinder_page_white_get_5593.png iconfinder_page_white_put_5609.png iconfinder_pencil_5631.png iconfinder_stop_5785.png iconfinder_arrow_down_4982.png iconfinder_arrow_up_4999.png iconfinder_asterisk_yellow_5001.png iconfinder_page_add_5548.png iconfinder_page_delete_5552.png iconfinder_textfield_rename_5877.png inst_adpcm.png inst_kit.png iconfinder_wrench_orange_5931.png iconfinder_shape_flip_horizontal_5740.png iconfinder_magifier_zoom_out_5483.png iconfinder_magnifier_zoom_in_5485.png iconfinder_eye_5333.png iconfinder_chart_line_edit_5152.png BambooTracker-0.6.5/BambooTracker/resources/icon/iconfinder_add_4941.png000066400000000000000000000013351476276175200260540ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<oIDAT8˥Ka[/Y()%X(olNۖskn.-h;8fEP"jïMGˈ}yພ羹$I.tulu AX:𼂒ZHh1DnZJOJB{Z?`2`S=N$ő=;a &jw qJG#<"N2h8޵`6xցn_+ ~Zto}`x%XЛ͈ hXѿƻ/}BJ_G&|Qr-6Aރ EL⬡\U3:WUh[C6+ 6.f *K͸ܝFq ou4܄?d|XҥMvD` *_[ #A20liR|xq`4w=\uQ m+G|%$5Թ5RO*YGMUO Gqj4ְ(X& s1c˭(LVf RdjQ '-1ATA>U j4,pV"4L$e@.ArBY a~myY])Q8tNLܞt2"I o=CSd)__AF(IENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/iconfinder_arrow_down_4982.png000066400000000000000000000005731476276175200275150ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe< IDAT8˥?KQi H*.EA!XX vc!v{;,ӻ\}Ï7Ibc0u2B^h gw`4+@jɥ s5"H hPǁh"H.EBFݜ& 5EɃZn+;l8r9~c$ h>*k>hLLjƳRwUd:<$hݸIENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/iconfinder_arrow_up_4999.png000066400000000000000000000005641476276175200272020ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˥?/Qϲhg5j%'F,[ LH 3(vw=fcoN{O~9VM4Q7ܿ)v/WQ=&bpSO ^'&^:\˨6eND!& 9꒣_|?\ srx,g*,(F#d[OaAA*P p1O+C$`)*w`A#0$ *?b&NRIENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/iconfinder_asterisk_yellow_5001.png000066400000000000000000000013471476276175200305330ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<yIDAT8˥KTaƟa-j9ijD ETQ@1 I&V);D-sD"̏QgRk9w5Z<9/Hb! ʼp,Ͼ3N?j5G+C.f%L*o3M`)8D{F[l9Z͹U}sud43bQ[RoIvr7ޤ q5c\|z*a$][ ,[RI"l12x'|FjaY/r A*f?LE ELwChP_ c5Pp)9fA7s 9/L~>7dS!{/V?@l*ydLކ ]Gp*{" 3tRI}/7K VtV.ۥPc >o"4TSH{Z6,<9 F}pL*f<҆-B]+,I+.~'sl#!lZuZѺl+wx !BNJH/:ePm%𴸗 L:%IENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/iconfinder_chart_line_edit_5152.png000066400000000000000000000013161476276175200304330ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<`IDAT8c?%,M_| zw_3:*WrlNC/-좕B'{ u_a46ҽbߡE%D47;ٻƩ;8ˣ}>6[ӕS@*Z Qk>~͵hB\9uxZvYb J Cيٽ?BYvn&kft$,d9Zap\^ Y7 QJF 9=Q4 ؜Io SBpsI) Fv(@yՎވc\@ %% Z2h'@d(<|áaJuM@O⤁LGjd!X8Af 5J i K->w62ƾWH}:mP]XB0QX=ib_g=!Ftt…clrIENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/iconfinder_eye_5333.png000066400000000000000000000013561476276175200261050ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8SKOQt:}L!-,@pcTFcT6,L4#\ 66ntF+DDn}Qܹ3z[!q“|{/߹"QBC=_t}R2|ۀ#S<~/~RWeB`B>۲qu:Fashl]?䷮Ҁ&LDmDX[tģ+ڈfl h*a`9ee;iՐ{DOx' 06m`yV&8nC >LɶU[nER*Ѡt 8 )Z,znq-r9EQY|PK V욃~p)ZmhE3&Q(6lc al0%l2$'ZM6}b h1\SUPa!Fp&۔)A>Zcbfrܟ;S D#фzT/_lsX0i#`>{nm%FȮ{O=ۏsu#R^j$Są69|Lw< tH.8iNK<#|fUoml`eAIENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/iconfinder_magifier_zoom_out_5483.png000066400000000000000000000012211476276175200310360ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<#IDAT8˥oHae^'", ZD(z^XًQFT(ˎgednn?Ά9w^6fv݊`E֋{xT?Nj^fZ&la σBIFx"Ȥ@%d0) LRԗ<i&g.;pDddfD<lY ֱ/hbRT7%`8*|1ZD؊uߘ2*\n: ym|nێ Z6U:!#ʭC;ێdwx}3֋HԬDIN?ɂTd 2OzsQ^.C1H"勻-)>El4:Dokn H(_L@qw4W4M.LXzn zG,iˏBlSopN㋷荔x 0[;Z[M[Ol6wYá_TXmXcBzXP@V}np t[c⚲|mEIENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/iconfinder_magnifier_zoom_in_5485.png000066400000000000000000000012501476276175200310170ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<:IDAT8˥[hqƇ]D]YhXtUXZjHc!$D LkXk[+ۚ}n$sjtI}/oɯ3kݦ%4(0~ _ <=qfz!xWAԮl4seUjbHk]Y͝˗:te%k~IIENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/iconfinder_page_add_5548.png000066400000000000000000000013431476276175200270530ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<uIDATMU=ߙ;cYA!Y؟H*FEnZ)U-KTrYbj?DVN){ʅt% }6q2=V}.vDžw}z᷶/ bPfvf`džY:LI@A JD(JΜ#w o^7=  BFh}RBdq\ֆX=)jCL5QB ZBP!AD6&:@ч,!dLP Z]ABP$)H ~bԜ/G&FdHВ:}soUkZq7+qj^a(d4'?/g:x#m;+ǽgvf}7->hgߞ1+qkKENݯ_4K'S5 XcZ*JJ#~0[@jӲ^~18--&;T߶řlw^~p=,+2tD] "(669]f߅I@A JD(JwɆ^߹V7=  BFh}RBd4jcS1Xe }O$hɍ#N}ྦྷ3K?}bB8L A##=}-`xU6o_ qu]ђ>郖[6༮ akrԋoZ%Wλ1G_4\::]*,/_w4E+;}V IL}oG)g$[?/`/} -%nQP`IENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/iconfinder_page_white_get_5593.png000066400000000000000000000010041476276175200302740ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˕MKQM!Z M [.PladdFqѢ@ ms5?0ssaka`0%0Xl4f# %Hj ۔J% iYJ :$$ Z $ORX,"A@D"Y# (P.f{ZjD!`m7;JdA7j UB jfB z.υ$ĿT C] (@Np>]P8c6 #֍+ޥ/>?>/'nqgջ7lQ+LGՂAsXzߋkf껰Ȇij}/bg<7G!Z#ɠlrs`%Co4tQЛ1IENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/iconfinder_page_white_put_5609.png000066400000000000000000000010131476276175200303230ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˕KKaE"!hQAIe1- J \ jBA Rh-\HhA(::ۜ309g822P h0,0@ YU8.U**rv%@C\.eki$ID"Qx$tI@ Ţx',RѨ(0 #JZ 2 bXKKՙBL}KqP(YL*y$jfB\HB-*vױ}o9 ӭBT%h (L<ѧǨvdx:ٿ {Њ7'\;TS;s`ۄ#\0?vaV2+:.ŷw - ZeR:ؕߓֱ+(\ҴphIENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/iconfinder_pencil_5631.png000066400000000000000000000007021476276175200265700ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<TIDAT8˭?Hq][ p@\hZr0+ʐܲHlHH*$:2,S *A = _B\x7ÖZc<oozڈBЊ" 9!B!>gvڀ,;SRNXjD|D62 ( 8`~vPOoCvs 8 ]|aɄ[ z/Nİ*tuXİ*QT.;kDbՀ̱ <ݏy#~=庠q]}fĝZtlT3R%~>#w -;06m7wU\&IENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/iconfinder_shape_flip_horizontal_5740.png000066400000000000000000000006231476276175200317040ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<%IDAT8=JQ31>4-M .z nf@D .BA4)Li)I9‰`Y;1OJ3ݍma9 =1!cʓM2y^$&Ak&J)LccJNv#DF- PVʝUkrL X̔nf6Co ? 0 c J)H) g'ι-x [@(űGqn,'4j'kɱyY rdB$sޭ8؟IENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/iconfinder_wrench_orange_5931.png000066400000000000000000000011101476276175200301340ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˕kSQ8H)N]uA,wjazcbb6& Qcn^b4b2e l xW }$9Xt:$n"0T8J)N!h4ʉF|p8|2X,f[͵`0S~OZ% } iz=nHXt:4 "BrLRtv_T'-lu&f_uRl6 u]e 5~lޢnz{ejjUiw|}?$&x r|TN%*4Ç l;'T^ٗG <"pFୌw!yhݔeE6  ܙצ PJ:C\DW^,<<4 A ѹ]s6ޑIENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/if_arrow_redo_4989.png000066400000000000000000000011611476276175200257560ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˥OHQ7Zcf9UԢت fdPlQ RȕnE"à6e-r#apd6Yc|lDe{yDQؿ<>3;uDٖ47KgAg)g$ujUO 2kp s܍ { t?<(7YՔLX]`$7id? 6 \O_ٿ눊>IENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/if_cog_5175.png000066400000000000000000000010001476276175200243370ustar00rootroot00000000000000PNG  IHDR7gAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT(UQMKa^ tT2 .EQС:EeQPR麖QRq%!O^wm!c333 To^%ۚ\Y|gz),{ b,پ7g| fʨ'TǂLGUM W0FDNC \`BASJk4$0SrI ZHd (cO:/B&69I#M:X61đ|hK5AJTȐ9Zf) 9+<OQ  **4KrHk?WDj L>c!i9BL%^0n9ֈs>i ֓i붻k>Gj ]IENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/if_cut_red_5249.png000066400000000000000000000012121476276175200252230ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8c?.=йnBI03vEf3Db'r"'Lb#U^.8Y&6Mǻai n^S)S6,9:yV h__sSb}>wՇ)&e@pƖsWHZЭ7^kZYMt lMskHNSl3t=7? jIS'24`3n'qXMdayVgHg4఍ћzAR3/8/]|5ԿB~7/VZ;iot꡻^hdrѧ6.҂' q(`5`$q[灊VkojآVN2Sڙc }lj]܌ lܽnrcпe}N`Lɠ+?TrIENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/if_disk_5276.png000066400000000000000000000011541476276175200245350ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT=]Uе;C`DD Ve h*B"6VV"c vba"NB ޙ{~jo|x&,K*o=]߻_v<}tu.R[Ҝ9Xsy|o~ |u_Bk6Yf7bQڴ^Й<T::֊Ǧ7Uݕuwٞ ,6}4'kٞF%*T⏿NHZ'}&NGۯ_鋾ﴌ*UD:tHُZkƦ/F%8A#хJpFQ$Q5:H0' QEDDK$e]t4IJDUS*!BxKOƈ5Hup9Q pҫj'ɬ4nx93W3j7˜oO$*yHI$H=m?!|nmIENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/if_folder_5362.png000066400000000000000000000010311476276175200250440ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8œA[EtQg7wALCWA0P017p2=:A3cb2'pܪ$Vme@ ݬ2OTO1W/`z8% ;O;9P#9B}^nO;Ǫo~ d~E dpɳ__jMMIENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/if_paste_plain_5629.png000066400000000000000000000011351476276175200261030ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8ˍ?hSQ//&%h?IDH ., .\B7ApN.:H:b'KEjim޻|ɋi{n$ s私O FT*V:z;z-/A@Xz晱R$a]$1sq HXidgAĖP8! \n#<𴃧 '">`bPw |?2MZrw 3R$$ 8m6K}!E(8nt:HY_ !ׁ9[PV^ Pp xR1.; ,;ہ!(>$D;x&)BSDVgwxvu \yv*)DQ4q6;;33;0Ezdk7IENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/if_resultset_last_5687.png000066400000000000000000000010141476276175200266610ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8œkQ7; *,E/A^TI%XX,,?H6ɨA QL&q>X&_q81)%v]j1W٫G ;E)}z>wOu{G%lj٘=*l__v$>lIENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/if_resultset_next_5688.png000066400000000000000000000006131476276175200267010ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8c?%4~nv xf%e@gמx}Ë Sl@ނGWK_%<\dD`Z~'UKX#꺝([m/ػ@J:t6%y]Dr4JDo]x濨d%$1ED*뿀IENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/if_shape_square_5750.png000066400000000000000000000005411476276175200262570ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8œ1N@DNJ13$$\r;]^kl4Ngv'{wR(Q˴3x~zDĀ& |^hTtDI%¥V9X$ p1lĠR47/u6!ͣAZtl3֠*kFrKzW9 P`XquguB`~#NH>k$X =o7oWem,OIENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/if_table_edit_5800.png000066400000000000000000000013501476276175200256660ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<zIDAT8˥YHQeƲ1k2 6+mq Z^" "|Z Az A_ ,*u&\(4BetԱt(AQtss 5lkGOk UR @( 4B["F*HnPZ K7>nL0Jt_A(W \NW)w'2i@܇Uo1+R*|wdmKGmdG:7CoHn`D^1{Rs61i5m.T(-]?ei0#b#sjk8]oԄ ؖBj^9H 6􌉁V1wo6&PV`P&eP b/<0W#E,dm55IO2ʼnf3l)8WoPq-ӠiWG'Dz^*,f*o̫S" 2kt/Fߴ]RInb{ưL2.aRM*(%'HGM>Խ)[x6E`&8;N]^."9{;S LLV7 IIENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/inst_fm.png000066400000000000000000000002151476276175200240760ustar00rootroot00000000000000PNG  IHDRaTIDAT8Ocd0Ra8_?zx0~a 1EY!l066[,d/\B5/P 7EHnL3d@IENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/inst_kit.png000066400000000000000000000002071476276175200242640ustar00rootroot00000000000000PNG  IHDRaNIDAT8Ocd0Ra8^?(o0qX+D7fP ux @. &za4%RF\RIENDB`BambooTracker-0.6.5/BambooTracker/resources/icon/inst_ssg.png000066400000000000000000000002251476276175200242710ustar00rootroot00000000000000PNG  IHDRa\IDAT8Ocd0Ra HBFAȚ`|5"lE2%aNG[X,#:] pIHd(D4IENDB`BambooTracker-0.6.5/BambooTracker/resources/resources.pri000066400000000000000000000003771476276175200235400ustar00rootroot00000000000000# QMake wrapper over compiled-in Qt Resource definitions RESOURCES += \ $$PWD/doc/doc.qrc \ $$PWD/icon/icon.qrc # Platform-specific app icon win32 { RC_ICONS = $$PWD/icon/BambooTracker.ico } else:osx { ICON = $$PWD/icon/BambooTracker.icns } BambooTracker-0.6.5/BambooTracker/song_length_calculator.cpp000066400000000000000000000141541476276175200242220ustar00rootroot00000000000000/* * Copyright (C) 2020-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "song_length_calculator.hpp" #include #include #include #include #include "module.hpp" #include "effect.hpp" #include "enum_hash.hpp" namespace { inline double calculateStrictStepTicks(int rate, int tempo, int speed) { return 2.5 * rate * speed / tempo; } } SongLengthCalculator::SongLengthCalculator(Module& mod, int songNum) : mod_(mod), songNum_(songNum) { } double SongLengthCalculator::approximateLengthBySecond() const { Song& song = mod_.getSong(songNum_); std::unordered_set visitedOrder; double tickCnt = 0.; const int rate = static_cast(mod_.getTickFrequency()); int tempo = song.getTempo(); int speed = song.getSpeed(); std::vector groove = mod_.getGroove(song.getGroove()); double stepTicks = calculateStrictStepTicks(rate, tempo, speed); size_t grooveIdx = 0; bool isTempo = song.isUsedTempo(); std::vector attribs = song.getTrackAttributes(); int orderNum = 0; int stepNum = 0; int maxOrder = static_cast(song.getOrderSize()); while (!visitedOrder.count(orderNum)) { visitedOrder.insert(orderNum); int maxStep = static_cast(song.getPatternSizeFromOrderNumber(orderNum)); std::unordered_map jumpEffMap; for (; stepNum < maxStep; ++stepNum) { std::unordered_map speedEffMap; // Load effects for (const TrackAttribute& attrib : attribs) { Step& step = song.getTrack(attrib.number).getPatternFromOrderNumber(orderNum).getStep(stepNum); for (int e = 0; e < Step::N_EFFECT; ++e) { const Effect&& eff = effect_utils::validateEffect(attrib.source, step.getEffect(e)); switch (eff.type) { case EffectType::SpeedTempoChange: case EffectType::Groove: speedEffMap[eff.type] = eff.value; break; case EffectType::PositionJump: case EffectType::SongEnd: case EffectType::PatternBreak: if (stepNum == maxStep - 1) jumpEffMap[eff.type] = eff.value; // Read only last step break; default: break; } } } // Update playback state auto&& it = speedEffMap.find(EffectType::SpeedTempoChange); if (it != speedEffMap.end()) { if (it->second < 0x20) { if (speed != it->second) { speed = it->second; isTempo = true; stepTicks = calculateStrictStepTicks(rate, tempo, speed); } } else if (tempo != it->second) { tempo = it->second; isTempo = true; stepTicks = calculateStrictStepTicks(rate, tempo, speed); } } for (const auto& eff : speedEffMap) { switch (eff.first) { case EffectType::Groove: if (eff.second < static_cast(mod_.getGrooveCount())) { groove = mod_.getGroove(eff.second); isTempo = false; grooveIdx = 0; } break; default: break; } } // Add step ticks if (isTempo) { tickCnt += stepTicks; } else { tickCnt += groove[grooveIdx]; ++grooveIdx %= groove.size(); } } // Update order position ++orderNum %= maxOrder; stepNum = 0; for (auto eff : jumpEffMap) { switch (eff.first) { case EffectType::PositionJump: if (eff.second < maxOrder) { orderNum = eff.second; } break; case EffectType::SongEnd: orderNum = 0; // To break order loop break; case EffectType::PatternBreak: if (eff.second < static_cast(song.getPatternSizeFromOrderNumber(orderNum))) { stepNum = eff.second; } break; default: break; } } } // Calculate time by seconds return tickCnt / rate; } void SongLengthCalculator::totalStepCount(size_t &introSize, size_t &loopSize) const { Song& song = mod_.getSong(songNum_); std::vector attribs = song.getTrackAttributes(); size_t totalStepCnt = 0; std::unordered_map stepCntLogMap; int lastOrder = static_cast(song.getOrderSize()) - 1; int curOrder = 0; for (int curStep = 0; !stepCntLogMap.count(curOrder); ) { // Count up size_t ptnFullSize = song.getPatternSizeFromOrderNumber(curOrder); stepCntLogMap[curOrder] = totalStepCnt; totalStepCnt += (ptnFullSize - curStep); // Check next order position const int odr = std::exchange(curOrder, (curOrder + 1) % (lastOrder + 1)); curStep = 0; for (const auto& attrib : attribs) { const Step& step = song.getTrack(attrib.number) .getPatternFromOrderNumber(odr).getStep(ptnFullSize - 1); for (int i = 0; i < Step::N_EFFECT; ++i) { Effect&& eff = effect_utils::validateEffect(attrib.source, step.getEffect(i)); switch (eff.type) { case EffectType::PositionJump: if (eff.value <= lastOrder) { curOrder = eff.value; curStep = 0; } break; case EffectType::SongEnd: // No loop introSize = totalStepCnt; loopSize = 0; return; case EffectType::PatternBreak: if (eff.value < static_cast(song.getPatternSizeFromOrderNumber(curOrder))) { curStep = eff.value; } break; default: break; } } } } // Calculate counts introSize = stepCntLogMap[curOrder]; loopSize = totalStepCnt - introSize; } BambooTracker-0.6.5/BambooTracker/song_length_calculator.hpp000066400000000000000000000026121476276175200242230ustar00rootroot00000000000000/* * Copyright (C) 2020-2021 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include class Module; class SongLengthCalculator { public: SongLengthCalculator(Module& mod, int songNum); double approximateLengthBySecond() const; void totalStepCount(size_t& introSize, size_t& loopSize) const; private: Module& mod_; const int songNum_; }; BambooTracker-0.6.5/BambooTracker/tick_counter.cpp000066400000000000000000000106311476276175200221670ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "tick_counter.hpp" TickCounter::TickCounter() : isPlaySong_(false), tempo_(150), // Dummy set tickRate_(60), // NTSC nextGroovePos_(-1), defStepSize_(6), // Dummy set curStepSize_(6), // Dummy set restTickToNextStep_(0), tickDiff_(0.f), tickDiffSum_(0.f), prevTickDiffSum_(0.f) { updateTickDifference(); } void TickCounter::setInterruptRate(uint32_t rate) { tickRate_ = static_cast(rate); updateTickDifference(); } void TickCounter::setTempo(int tempo) { tempo_ = tempo; updateTickDifference(); resetRest(); } int TickCounter::getTempo() const noexcept { return tempo_; } void TickCounter::setSpeed(int speed) { defStepSize_ = speed; updateTickDifference(); // Changing speed does not reset tempo, so don't reset tickDiffSum_ either. resetRest(); } int TickCounter::getSpeed() const noexcept { return defStepSize_; } void TickCounter::setGroove(const std::vector& seq) { grooves_ = seq; if (nextGroovePos_ != -1) nextGroovePos_ = 0; } void TickCounter::setGrooveState(GrooveState state) { switch (state) { case GrooveState::ValidByGlobal: nextGroovePos_ = 0; if (isPlaySong_) { curStepSize_ = grooves_.at(0); restTickToNextStep_ = curStepSize_ - 1; } break; case GrooveState::ValidByLocal: nextGroovePos_ = 0; resetRest(); break; case GrooveState::Invalid: nextGroovePos_ = -1; resetRest(); break; } // When enabling groove (which disables tempo), reset tempo accumulator. if (state == GrooveState::ValidByGlobal || state == GrooveState::ValidByLocal) { tickDiffSum_ = 0.f; prevTickDiffSum_ = 0.f; } } bool TickCounter::getGrooveEnabled() const noexcept { return (nextGroovePos_ != -1); } void TickCounter::setPlayState(bool isPlaySong) noexcept { isPlaySong_ = isPlaySong; } int TickCounter::countUp() { prevTickDiffSum_ = tickDiffSum_; if (isPlaySong_) { int ret = restTickToNextStep_; if (!restTickToNextStep_) { // When head of step, calculate real step size resetRest(); } else { --restTickToNextStep_; // Count down to next step } return ret; } else { return -1; } } void TickCounter::updateTickDifference() { float strictTicksPerStepByBpm = 10.0f * tickRate_ * defStepSize_ / (tempo_ << 2); tickDiff_ = strictTicksPerStepByBpm - static_cast(defStepSize_); } void TickCounter::resetCount() { restTickToNextStep_ = 0; tickDiffSum_ = 0; prevTickDiffSum_ = 0; } // MUST be idempotent when no groove is active. void TickCounter::resetRest() { if (nextGroovePos_ == -1) { tickDiffSum_ = prevTickDiffSum_ + tickDiff_; int castedTickDifSum = static_cast(tickDiffSum_); restTickToNextStep_ = defStepSize_ + castedTickDifSum; tickDiffSum_ -= castedTickDifSum; } else { restTickToNextStep_ = grooves_.at(static_cast(nextGroovePos_)); nextGroovePos_ = (nextGroovePos_ + 1) % static_cast(grooves_.size()); } curStepSize_ = restTickToNextStep_; // Ensure each row lasts for at least 1 tick, and that we can subtract 1 safely. if (restTickToNextStep_ < 1) curStepSize_ = restTickToNextStep_ = 1; // If resetRest() is called in response to the tracker processing a row or Fxx/Oxx event, // subtract 1 so countUp() will remain on the row for (speed - 1) subsequent ticks. if (isPlaySong_) { restTickToNextStep_--; } } int TickCounter::getCountsInCurrentStep() const noexcept { return curStepSize_; } BambooTracker-0.6.5/BambooTracker/tick_counter.hpp000066400000000000000000000040771476276175200222030ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include enum class GrooveState { ValidByGlobal, ValidByLocal, Invalid }; class TickCounter { public: TickCounter(); void setInterruptRate(uint32_t rate); void setTempo(int tempo); int getTempo() const noexcept; void setSpeed(int speed); int getSpeed() const noexcept; void setGroove(const std::vector& seq); void setGrooveState(GrooveState state); bool getGrooveEnabled() const noexcept; void setPlayState(bool isPlaySong) noexcept; /// Reuturn: /// -1: not tick or step /// 0: head of step /// 0<: rest tick count to next step int countUp(); void resetCount(); int getCountsInCurrentStep() const noexcept; private: bool isPlaySong_; int tempo_; int tickRate_; std::vector grooves_; int nextGroovePos_; int defStepSize_, curStepSize_; int restTickToNextStep_; float tickDiff_; float tickDiffSum_; float prevTickDiffSum_; void updateTickDifference(); void resetRest(); }; BambooTracker-0.6.5/BambooTracker/utils.hpp000066400000000000000000000106501476276175200206440ustar00rootroot00000000000000/* * Copyright (C) 2018-2022 Rerrah * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include #include namespace utils { template inline T clamp(T value, T low, T high) { return std::min(std::max(value, low), high); } template inline auto find(InputContainer& input, const T& value) { return std::find(input.begin(), input.end(), value); } template inline auto findIf(InputContainer& input, Predicate&& pred) { return std::find_if(input.begin(), input.end(), std::forward(pred)); } template , class InputIterator, class Predicate> inline auto findIndicesIf(InputIterator first, InputIterator last, Predicate pred) { using T = typename OutputContainer::value_type; OutputContainer idcs; for (auto it = first; (it = std::find_if(it, last, pred)) != last; ++it) { idcs.push_back(static_cast(std::distance(first, it))); } return idcs; } template , class InputContainer, class Predicate> inline auto findIndicesIf(const InputContainer& input, Predicate&& pred) { return findIndicesIf(input.begin(), input.end(), std::forward(pred)); } template