commit d21bc831e17ce6c0654684761995ef86975e1cf7 Author: Armin Date: Thu Jul 23 23:40:48 2026 +0200 init diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..49ab135 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,15 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +polar: # Replace with a single Polar username +buy_me_a_coffee: binyaminf +thanks_dev: # Replace with a single thanks.dev username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..fe5c322 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: Bug report +about: Create a report to help improve Just a Sample +title: "" +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**Plugin Version** +Hover your mouse over the plugin logo to show the version + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..9e70ff4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Feature Request + url: https://github.com/BOBONA/Just-a-Sample/discussions/46 + about: Please suggest new features here. diff --git a/.github/workflows/build_and_release.yml b/.github/workflows/build_and_release.yml new file mode 100644 index 0000000..e36e83a --- /dev/null +++ b/.github/workflows/build_and_release.yml @@ -0,0 +1,320 @@ +name: Build and Release Just A Sample + +on: + workflow_dispatch: + +jobs: + get-version: + runs-on: ubuntu-latest + outputs: + full_version: ${{ steps.calc.outputs.FULL_VERSION }} + base_version: ${{ steps.calc.outputs.BASE_VERSION }} + patch: ${{ steps.calc.outputs.PATCH }} + steps: + - uses: actions/checkout@v4 + + - id: calc + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + BASE_VERSION=$(cmake -DPRINT_VERSION=ON -P version.cmake 2>&1) + + RELEASE_DATA=$(gh release view "v${BASE_VERSION}-latest" --json isDraft,assets 2>/dev/null || echo "") + + if [ -z "$RELEASE_DATA" ]; then + PATCH=0 + else + IS_DRAFT=$(echo "$RELEASE_DATA" | jq -r '.isDraft') + ASSETS=$(echo "$RELEASE_DATA" | jq -r '.assets[].name' 2>/dev/null || echo "") + + HIGHEST_PATCH=$(echo "$ASSETS" | grep -oP "(?<=v${BASE_VERSION}\.)\d+" | sort -n | tail -1) + + if [ -z "$HIGHEST_PATCH" ]; then + PATCH=0 + elif [ "$IS_DRAFT" == "true" ]; then + PATCH=$HIGHEST_PATCH + else + PATCH=$((HIGHEST_PATCH + 1)) + fi + fi + + FULL_VERSION="${BASE_VERSION}.${PATCH}" + + echo "BASE_VERSION=$BASE_VERSION" >> $GITHUB_OUTPUT + echo "FULL_VERSION=$FULL_VERSION" >> $GITHUB_OUTPUT + echo "PATCH=$PATCH" >> $GITHUB_OUTPUT + + build-windows: + needs: get-version + runs-on: windows-latest + env: + CCACHE_DIR: ${{ github.workspace }}/.ccache + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Cache .deps, build, and ccache + uses: actions/cache@v5.0.3 + with: + path: | + out/build + .deps + ~/.ccache + key: ${{ runner.os }}-build-${{ hashFiles('CMakeLists.txt', '**/*.cmake') }} + restore-keys: ${{ runner.os }}-build- + + - name: Setup MSVC environment + uses: ilammy/msvc-dev-cmd@v1 + + - name: Install ccache + run: choco install ccache -y + + - name: Configure CMake + run: cmake --preset windows -DJAS_PATCH_INDEX=${{ needs.get-version.outputs.patch }} + + - name: Build Release + run: cmake --build --preset release-windows + + - name: Restore signing certificate + shell: pwsh + run: | + $cert = "${{ secrets.WINDOWS_PFX_BASE64 }}" + [IO.File]::WriteAllBytes("cert.pfx",[Convert]::FromBase64String($cert)) + + - name: Sign VST3 binary + run: | + signtool sign ` + /f cert.pfx ` + /p "${{ secrets.WINDOWS_PFX_PASSWORD }}" ` + /fd SHA256 ` + /tr http://timestamp.digicert.com ` + /td SHA256 ` + "out\build\windows\JustASample_artefacts\Release\VST3\Just a Sample.vst3\Contents\x86_64-win\Just a Sample.vst3" + + - name: Install 7-Zip + run: choco install 7zip -y + + - name: Zip Windows VST3 + run: | + cd "out\build\windows\JustASample_artefacts\Release\VST3" + 7z a -tzip "${{ github.workspace }}\JAS.Windows.VST3.v${{ needs.get-version.outputs.full_version }}.zip" "Just a Sample.vst3" + + - name: Build Windows Installer + run: iscc "Releases\Windows\installer_setup.iss" /DMyAppVersion="${{ needs.get-version.outputs.full_version }}" /DSourceDir="${{ github.workspace }}" + + - name: Sign installer + run: | + signtool sign ` + /f cert.pfx ` + /p "${{ secrets.WINDOWS_PFX_PASSWORD }}" ` + /fd SHA256 ` + /tr http://timestamp.digicert.com ` + /td SHA256 ` + "Releases\Windows\Output\Install Just a Sample.exe" + + - name: Cleanup certificate + run: del cert.pfx + + - name: Zip Windows Installer + run: | + cd "Releases\Windows\Output" + 7z a -tzip "${{ github.workspace }}\Install.Just.a.Sample.Windows.v${{ needs.get-version.outputs.full_version }}.zip" "Install Just a Sample.exe" + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: windows-artifacts + path: | + Install.Just.a.Sample.Windows.v${{ needs.get-version.outputs.full_version }}.zip + JAS.Windows.VST3.v${{ needs.get-version.outputs.full_version }}.zip + + build-linux: + needs: get-version + runs-on: ubuntu-22.04 + env: + CCACHE_DIR: ${{ github.workspace }}/.ccache + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Cache .deps, build, and ccache + uses: actions/cache@v5.0.3 + with: + path: | + out/build + .deps + ~/.ccache + key: ${{ runner.os }}-build-${{ hashFiles('CMakeLists.txt', '**/*.cmake') }} + restore-keys: ${{ runner.os }}-build- + + - name: Install JUCE Linux Dependencies + run: chmod +x Releases/Linux/install-dependencies.sh && ./Releases/Linux/install-dependencies.sh + + - name: Install ccache + run: sudo apt-get install ccache -y + + - name: Configure CMake + run: cmake --preset linux -DJAS_PATCH_INDEX=${{ needs.get-version.outputs.patch }} + + - name: Build Release + run: cmake --build --preset release-linux + + - name: Strip VST3 binary + run: strip "out/build/linux/JustASample_artefacts/Release/VST3/Just a Sample.vst3/Contents/x86_64-linux/Just a Sample.so" + + - name: Zip Linux VST3 + run: | + cd "out/build/linux/JustASample_artefacts/Release/VST3" + zip -r "${{ github.workspace }}/JAS.Linux.VST3.v${{ needs.get-version.outputs.full_version }}.zip" "Just a Sample.vst3" + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: linux-artifacts + path: JAS.Linux.VST3.v${{ needs.get-version.outputs.full_version }}.zip + + build-macos: + needs: get-version + runs-on: macos-latest + env: + CCACHE_DIR: ${{ github.workspace }}/.ccache + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Cache .deps, build, and ccache + uses: actions/cache@v5.0.3 + with: + path: | + out/build + .deps + ~/.ccache + key: ${{ runner.os }}-build-${{ hashFiles('CMakeLists.txt', '**/*.cmake') }} + restore-keys: ${{ runner.os }}-build- + + - name: Install ccache + run: brew install ccache + + - name: Configure CMake + run: cmake --preset macos -DJAS_PATCH_INDEX=${{ needs.get-version.outputs.patch }} + + - name: Build Release + run: cmake --build --preset release-macos + + - name: Install macOS packaging tools + run: | + brew install fileicon + python3 -m venv venv + source venv/bin/activate + pip3 install dmgbuild + + - name: Set File Icons + run: | + cp "Releases/macOS/icon.icns" "out/build/macos/JustASample_artefacts/Release/VST3/Just a Sample.vst3/Contents/Resources/Icon.icns" + cp "Releases/macOS/icon.icns" "out/build/macos/JustASample_artefacts/Release/AU/Just a Sample.component/Contents/Resources/Icon.icns" + + - name: Ad-Hoc Sign Binaries + run: | + codesign --force --deep -s - "out/build/macos/JustASample_artefacts/Release/VST3/Just a Sample.vst3" + codesign --force --deep -s - "out/build/macos/JustASample_artefacts/Release/AU/Just a Sample.component" + + - name: Zip macOS VST3 + run: | + cd "out/build/macos/JustASample_artefacts/Release/VST3" + zip -r "${{ github.workspace }}/JAS.macOS.VST3.v${{ needs.get-version.outputs.full_version }}.zip" "Just a Sample.vst3" + + - name: Zip macOS AU + run: | + cd "out/build/macos/JustASample_artefacts/Release/AU" + zip -r "${{ github.workspace }}/JAS.macOS.AU.v${{ needs.get-version.outputs.full_version }}.zip" "Just a Sample.component" + + - name: Stage files for PKG + run: | + mkdir -p pkg_root/Library/Audio/Plug-Ins/VST3 + mkdir -p pkg_root/Library/Audio/Plug-Ins/Components + cp -R "out/build/macos/JustASample_artefacts/Release/VST3/Just a Sample.vst3" "pkg_root/Library/Audio/Plug-Ins/VST3/" + cp -R "out/build/macos/JustASample_artefacts/Release/AU/Just a Sample.component" "pkg_root/Library/Audio/Plug-Ins/Components/" + + - name: Build PKG Installer + run: | + pkgbuild --root pkg_root \ + --identifier com.BinyaminFriedman.JustASample \ + --version "${{ needs.get-version.outputs.full_version }}" \ + --install-location / \ + "Just a Sample.pkg" + + - name: Set PKG Icon + run: | + fileicon set "Just a Sample.pkg" "Releases/macOS/installer_icon.png" + + - name: Create Clean DMG + run: | + mkdir dmg_root + cp "Just a Sample.pkg" dmg_root/ + + source venv/bin/activate + python3 -m dmgbuild -s Releases/macOS/dmg_settings.py "Install Just a Sample" Install.Just.a.Sample.macOS.v${{ needs.get-version.outputs.full_version }}.dmg + fileicon set "Install.Just.a.Sample.macOS.v${{ needs.get-version.outputs.full_version }}.dmg" "Releases/macOS/icon.png" + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: macos-artifacts + path: | + Install.Just.a.Sample.macOS.v${{ needs.get-version.outputs.full_version }}.dmg + JAS.macOS.VST3.v${{ needs.get-version.outputs.full_version }}.zip + JAS.macOS.AU.v${{ needs.get-version.outputs.full_version }}.zip + + publish-release: + needs: [get-version, build-windows, build-linux, build-macos] + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + merge-multiple: true + + - name: Delete old assets + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + ASSETS=$(gh release view v${{ needs.get-version.outputs.base_version }}-latest --json assets -q '.assets[].name' || true) + for asset in $ASSETS; do + gh release delete-asset v${{ needs.get-version.outputs.base_version }}-latest "$asset" -y + done + + - name: Create or Update Release + uses: ncipollo/release-action@v1.20.0 + with: + tag: "v${{ needs.get-version.outputs.base_version }}-latest" + name: "Just A Sample v${{ needs.get-version.outputs.base_version }} Latest" + artifacts: "artifacts/*" + allowUpdates: true + replacesArtifacts: true + makeLatest: true + draft: false + omitBodyDuringUpdate: true + + - name: Download Butler + run: | + curl -L -o butler.zip https://broth.itch.zone/butler/linux-amd64/LATEST/archive/default + unzip butler.zip -d butler + echo "${{ github.workspace }}/butler" >> $GITHUB_PATH + + - name: Publish to itch.io + env: + BUTLER_API_KEY: ${{ secrets.BUTLER_API_KEY }} + run: | + butler push artifacts/Install.Just.a.Sample.Windows.v${{ needs.get-version.outputs.full_version }}.zip binyaminf/just-a-sample:windows-easy-installer --userversion ${{ needs.get-version.outputs.full_version }} + butler push artifacts/Install.Just.a.Sample.macOS.v${{ needs.get-version.outputs.full_version }}.dmg binyaminf/just-a-sample:macos-easy-installer --userversion ${{ needs.get-version.outputs.full_version }} + butler push artifacts/JAS.Windows.VST3.v${{ needs.get-version.outputs.full_version }}.zip binyaminf/just-a-sample:windows-vst3 --userversion ${{ needs.get-version.outputs.full_version }} + butler push artifacts/JAS.macOS.VST3.v${{ needs.get-version.outputs.full_version }}.zip binyaminf/just-a-sample:macos-vst3 --userversion ${{ needs.get-version.outputs.full_version }} + butler push artifacts/JAS.macOS.AU.v${{ needs.get-version.outputs.full_version }}.zip binyaminf/just-a-sample:macos-au --userversion ${{ needs.get-version.outputs.full_version }} + butler push artifacts/JAS.Linux.VST3.v${{ needs.get-version.outputs.full_version }}.zip binyaminf/just-a-sample:linux-vst3 --userversion ${{ needs.get-version.outputs.full_version }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c0b221e --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +# Build directories +/build/ +/out/ + +# CMake generated files +CMakeFiles/ +CMakeCache.txt +cmake_install.cmake + +# CMake build dependencies (FetchContent) +.deps/ + +# IDEs and editors +**/.idea/ +.vscode/ +*.swp +*.swo +*~ +\#*\# +.#* + +# OS files +.DS_Store +Thumbs.db + +# Build artifacts +*.o +*.obj +*.a +*.lib +*.so +*.dylib +*.dll +*.exe +*.out + +# Project specific +/Releases/Windows/Secrets/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a1ef069 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "External/readerwriterqueue"] + path = External/readerwriterqueue + url = https://github.com/cameron314/readerwriterqueue/ diff --git a/Assets/Features/Configure Record.png b/Assets/Features/Configure Record.png new file mode 100644 index 0000000..41412bb Binary files /dev/null and b/Assets/Features/Configure Record.png differ diff --git a/Assets/Features/Dark Mode.png b/Assets/Features/Dark Mode.png new file mode 100644 index 0000000..2e7855b Binary files /dev/null and b/Assets/Features/Dark Mode.png differ diff --git a/Assets/Features/Detect Pitch.png b/Assets/Features/Detect Pitch.png new file mode 100644 index 0000000..912635e Binary files /dev/null and b/Assets/Features/Detect Pitch.png differ diff --git a/Assets/Features/Device Settings.png b/Assets/Features/Device Settings.png new file mode 100644 index 0000000..4b4ae6d Binary files /dev/null and b/Assets/Features/Device Settings.png differ diff --git a/Assets/Features/Editor and Navigator.png b/Assets/Features/Editor and Navigator.png new file mode 100644 index 0000000..08a8f90 Binary files /dev/null and b/Assets/Features/Editor and Navigator.png differ diff --git a/Assets/Features/Effects.png b/Assets/Features/Effects.png new file mode 100644 index 0000000..c7bca2b Binary files /dev/null and b/Assets/Features/Effects.png differ diff --git a/Assets/Features/Extreme Zoom.png b/Assets/Features/Extreme Zoom.png new file mode 100644 index 0000000..0d24d35 Binary files /dev/null and b/Assets/Features/Extreme Zoom.png differ diff --git a/Assets/Features/Footer.png b/Assets/Features/Footer.png new file mode 100644 index 0000000..edcc973 Binary files /dev/null and b/Assets/Features/Footer.png differ diff --git a/Assets/Features/Playback Controls.png b/Assets/Features/Playback Controls.png new file mode 100644 index 0000000..b09a749 Binary files /dev/null and b/Assets/Features/Playback Controls.png differ diff --git a/Assets/Features/Plugin UI Features.png b/Assets/Features/Plugin UI Features.png new file mode 100644 index 0000000..811226c Binary files /dev/null and b/Assets/Features/Plugin UI Features.png differ diff --git a/Assets/Features/Plugin UI v1.2.png b/Assets/Features/Plugin UI v1.2.png new file mode 100644 index 0000000..485e067 Binary files /dev/null and b/Assets/Features/Plugin UI v1.2.png differ diff --git a/Assets/Features/Plugin UI v1.png b/Assets/Features/Plugin UI v1.png new file mode 100644 index 0000000..a7df3cd Binary files /dev/null and b/Assets/Features/Plugin UI v1.png differ diff --git a/Assets/Features/Recording.png b/Assets/Features/Recording.png new file mode 100644 index 0000000..3777758 Binary files /dev/null and b/Assets/Features/Recording.png differ diff --git a/Assets/Features/Waveform Mode.png b/Assets/Features/Waveform Mode.png new file mode 100644 index 0000000..5c0cd70 Binary files /dev/null and b/Assets/Features/Waveform Mode.png differ diff --git a/Assets/Fonts/InriaSans-Bold.ttf b/Assets/Fonts/InriaSans-Bold.ttf new file mode 100644 index 0000000..06032d5 Binary files /dev/null and b/Assets/Fonts/InriaSans-Bold.ttf differ diff --git a/Assets/Fonts/InriaSans-Regular.ttf b/Assets/Fonts/InriaSans-Regular.ttf new file mode 100644 index 0000000..387d683 Binary files /dev/null and b/Assets/Fonts/InriaSans-Regular.ttf differ diff --git a/Assets/Fonts/Inter-Bold.ttf b/Assets/Fonts/Inter-Bold.ttf new file mode 100644 index 0000000..fe23eeb Binary files /dev/null and b/Assets/Fonts/Inter-Bold.ttf differ diff --git a/Assets/Fonts/Inter-Regular.ttf b/Assets/Fonts/Inter-Regular.ttf new file mode 100644 index 0000000..5e4851f Binary files /dev/null and b/Assets/Fonts/Inter-Regular.ttf differ diff --git a/Assets/Icons/IconAdd.svg b/Assets/Icons/IconAdd.svg new file mode 100644 index 0000000..7f55958 --- /dev/null +++ b/Assets/Icons/IconAdd.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Assets/Icons/IconDarkMode.svg b/Assets/Icons/IconDarkMode.svg new file mode 100644 index 0000000..879163e --- /dev/null +++ b/Assets/Icons/IconDarkMode.svg @@ -0,0 +1,3 @@ + + + diff --git a/Assets/Icons/IconDetect.svg b/Assets/Icons/IconDetect.svg new file mode 100644 index 0000000..3fe1561 --- /dev/null +++ b/Assets/Icons/IconDetect.svg @@ -0,0 +1,3 @@ + + + diff --git a/Assets/Icons/IconDrag.svg b/Assets/Icons/IconDrag.svg new file mode 100644 index 0000000..01939cb --- /dev/null +++ b/Assets/Icons/IconDrag.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Assets/Icons/IconFit.svg b/Assets/Icons/IconFit.svg new file mode 100644 index 0000000..63e8161 --- /dev/null +++ b/Assets/Icons/IconFit.svg @@ -0,0 +1,3 @@ + + + diff --git a/Assets/Icons/IconHelp.svg b/Assets/Icons/IconHelp.svg new file mode 100644 index 0000000..f8e24ce --- /dev/null +++ b/Assets/Icons/IconHelp.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/Assets/Icons/IconHideFX.svg b/Assets/Icons/IconHideFX.svg new file mode 100644 index 0000000..60bea74 --- /dev/null +++ b/Assets/Icons/IconHideFX.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Assets/Icons/IconLightMode.svg b/Assets/Icons/IconLightMode.svg new file mode 100644 index 0000000..24d6362 --- /dev/null +++ b/Assets/Icons/IconLightMode.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Assets/Icons/IconLinkEnabled.svg b/Assets/Icons/IconLinkEnabled.svg new file mode 100644 index 0000000..5f888d3 --- /dev/null +++ b/Assets/Icons/IconLinkEnabled.svg @@ -0,0 +1,3 @@ + + + diff --git a/Assets/Icons/IconLofi.svg b/Assets/Icons/IconLofi.svg new file mode 100644 index 0000000..4165760 --- /dev/null +++ b/Assets/Icons/IconLofi.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/Assets/Icons/IconLogo.svg b/Assets/Icons/IconLogo.svg new file mode 100644 index 0000000..0034ca6 --- /dev/null +++ b/Assets/Icons/IconLogo.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Assets/Icons/IconLoop.svg b/Assets/Icons/IconLoop.svg new file mode 100644 index 0000000..093d61b --- /dev/null +++ b/Assets/Icons/IconLoop.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Assets/Icons/IconLoopStart.svg b/Assets/Icons/IconLoopStart.svg new file mode 100644 index 0000000..97f4524 --- /dev/null +++ b/Assets/Icons/IconLoopStart.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Assets/Icons/IconMono.svg b/Assets/Icons/IconMono.svg new file mode 100644 index 0000000..3af4978 --- /dev/null +++ b/Assets/Icons/IconMono.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Assets/Icons/IconPin.svg b/Assets/Icons/IconPin.svg new file mode 100644 index 0000000..fd97102 --- /dev/null +++ b/Assets/Icons/IconPin.svg @@ -0,0 +1,3 @@ + + + diff --git a/Assets/Icons/IconPlay.svg b/Assets/Icons/IconPlay.svg new file mode 100644 index 0000000..9d365f3 --- /dev/null +++ b/Assets/Icons/IconPlay.svg @@ -0,0 +1,3 @@ + + + diff --git a/Assets/Icons/IconPreFX.svg b/Assets/Icons/IconPreFX.svg new file mode 100644 index 0000000..80e136a --- /dev/null +++ b/Assets/Icons/IconPreFX.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Assets/Icons/IconRecordSettings.svg b/Assets/Icons/IconRecordSettings.svg new file mode 100644 index 0000000..9179527 --- /dev/null +++ b/Assets/Icons/IconRecordSettings.svg @@ -0,0 +1,3 @@ + + + diff --git a/Assets/Icons/IconShowFX.svg b/Assets/Icons/IconShowFX.svg new file mode 100644 index 0000000..8918c59 --- /dev/null +++ b/Assets/Icons/IconShowFX.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Assets/Icons/IconSpeed.svg b/Assets/Icons/IconSpeed.svg new file mode 100644 index 0000000..602354f --- /dev/null +++ b/Assets/Icons/IconSpeed.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Assets/Icons/IconWaveformMode.svg b/Assets/Icons/IconWaveformMode.svg new file mode 100644 index 0000000..aed7e05 --- /dev/null +++ b/Assets/Icons/IconWaveformMode.svg @@ -0,0 +1,3 @@ + + + diff --git a/Assets/Icons/Logo.svg b/Assets/Icons/Logo.svg new file mode 100644 index 0000000..3a52ed0 --- /dev/null +++ b/Assets/Icons/Logo.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Assets/Icons/PaddedLogo.svg b/Assets/Icons/PaddedLogo.svg new file mode 100644 index 0000000..4ca3266 --- /dev/null +++ b/Assets/Icons/PaddedLogo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Assets/Promotional/itch_front.png b/Assets/Promotional/itch_front.png new file mode 100644 index 0000000..0eab213 Binary files /dev/null and b/Assets/Promotional/itch_front.png differ diff --git a/Assets/Promotional/screenshot1.png b/Assets/Promotional/screenshot1.png new file mode 100644 index 0000000..51393a4 Binary files /dev/null and b/Assets/Promotional/screenshot1.png differ diff --git a/Assets/Promotional/screenshot2.png b/Assets/Promotional/screenshot2.png new file mode 100644 index 0000000..4e74f38 Binary files /dev/null and b/Assets/Promotional/screenshot2.png differ diff --git a/Assets/Promotional/screenshot3.png b/Assets/Promotional/screenshot3.png new file mode 100644 index 0000000..61b2c6e Binary files /dev/null and b/Assets/Promotional/screenshot3.png differ diff --git a/Assets/Promotional/screenshot4.png b/Assets/Promotional/screenshot4.png new file mode 100644 index 0000000..9414363 Binary files /dev/null and b/Assets/Promotional/screenshot4.png differ diff --git a/Assets/Promotional/screenshot5.png b/Assets/Promotional/screenshot5.png new file mode 100644 index 0000000..12ea3a3 Binary files /dev/null and b/Assets/Promotional/screenshot5.png differ diff --git a/CMake/generate_build_info.cmake b/CMake/generate_build_info.cmake new file mode 100644 index 0000000..ef1042c --- /dev/null +++ b/CMake/generate_build_info.cmake @@ -0,0 +1,22 @@ +execute_process(COMMAND git describe --always --dirty --exclude=* + WORKING_DIRECTORY "${SOURCE_DIR}" OUTPUT_VARIABLE GIT_HASH OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) +if(NOT GIT_HASH) + set(GIT_HASH "unknown") +endif() + +execute_process(COMMAND git symbolic-ref -q HEAD + WORKING_DIRECTORY "${SOURCE_DIR}" OUTPUT_VARIABLE GIT_REF OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) +if(NOT GIT_REF) + set(DETACHED "1") +else() + set(DETACHED "0") +endif() + +string(TIMESTAMP BUILD_TIME "%Y-%m-%d_%H-%M-%S") + +file(WRITE "${OUTPUT_FILE}" + "// Auto-generated at build time. Do not edit.\n" + "#pragma once\n" + "#define JAS_GIT_INFO \"${GIT_HASH}-detached=${DETACHED}\"\n" + "#define JAS_BUILD_TIME \"${BUILD_TIME}\"\n" +) diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9c8af27 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,371 @@ +cmake_minimum_required(VERSION 3.22) + +# High-level configuration +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +option(USE_CCACHE "Enable compiler caching via ccache" OFF) +if (USE_CCACHE) + find_program(CCACHE_PROGRAM ccache) + if (CCACHE_PROGRAM) + set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_PROGRAM}) + set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_PROGRAM}) + endif() +endif() + +include(version.cmake) +if (DEFINED JAS_PATCH_INDEX) + set(APP_VERSION "${APP_VERSION}.${JAS_PATCH_INDEX}") +endif() +project(JUST_A_SAMPLE VERSION ${APP_VERSION}) + +set(FETCHCONTENT_BASE_DIR ${CMAKE_SOURCE_DIR}/.deps CACHE PATH "") +set(FETCHCONTENT_UPDATES_DISCONNECTED ON) +include(FetchContent) + +# Generate BuildInfo.h at build time so the timestamp reflects the actual build +set(BUILD_INFO_HEADER "${CMAKE_BINARY_DIR}/BuildInfo.h") + +# Custom command with OUTPUT for proper dependency tracking +add_custom_command(OUTPUT "${BUILD_INFO_HEADER}" + COMMAND ${CMAKE_COMMAND} -DSOURCE_DIR=${CMAKE_SOURCE_DIR} -DOUTPUT_FILE=${BUILD_INFO_HEADER} + -P ${CMAKE_SOURCE_DIR}/CMake/generate_build_info.cmake + DEPENDS ${CMAKE_SOURCE_DIR}/CMake/generate_build_info.cmake +) + +# Phony target that always runs, forcing the custom command to re-execute +add_custom_target(RegenerateBuildInfo + COMMAND ${CMAKE_COMMAND} -DSOURCE_DIR=${CMAKE_SOURCE_DIR} -DOUTPUT_FILE=${BUILD_INFO_HEADER} + -P ${CMAKE_SOURCE_DIR}/CMake/generate_build_info.cmake +) + +# GenerateBuildInfo depends on the header, which triggers the custom command. +# RegenerateBuildInfo forces a fresh timestamp on every build. +add_custom_target(GenerateBuildInfo ALL DEPENDS "${BUILD_INFO_HEADER}") +add_dependencies(GenerateBuildInfo RegenerateBuildInfo) + +# Dependency versions +set(JUCE_VERSION 8.0.12) +set(BUNGEE_INSTALL_VERSION v2.4.10) +set(MELATONIN_BLUR_VERSION origin/main) +set(MELATONIN_INSPECTOR_VERSION origin/main) +set(LEAF_VERSION 8d86c4e96ac48740da34f24c9f995bc3c4b3b2a0) + +# JAS build options +option(JAS_DARKMODE_DEFAULT "Set the default theme to dark mode" ON) +option(JAS_VST3_REAPER_INTEGRATION "Enable Reaper-specific VST3 extensions (Windows only)" OFF) +option(JAS_FAST_MATH "Enable fast-math in Release" ON) +option(JAS_ENABLE_AVX2 "Enable AVX2/FMA SIMD in Release" ON) + +if (JAS_ENABLE_AVX2 AND APPLE AND "arm64" IN_LIST CMAKE_OSX_ARCHITECTURES AND "x86_64" IN_LIST CMAKE_OSX_ARCHITECTURES) + message(WARNING "AVX2 is not compatible with universal builds on macOS. Disabling JAS_ENABLE_AVX2.") + set(JAS_ENABLE_AVX2 OFF) +endif() + +if (JAS_VST3_REAPER_INTEGRATION AND NOT WIN32) + set(JUCE_VST3_REAPER_INTEGRATION OFF) +endif() + +# Bungee build options +set(BUNGEE_BUILD_SHARED_LIBRARY OFF CACHE INTERNAL "") +set(STRETCHER_BUNGEE_MAX_OCTAVES 4) + +# Paths to patch files (careful editing these, the whitespace is important for unified diffs to work correctly) +set(BUNGEE_PATCH_FILE ${CMAKE_CURRENT_SOURCE_DIR}/Patches/bungee_lower_cmake_and_set_num_octaves.patch CACHE INTERNAL "") +set(LEAF_PATCH_FILE ${CMAKE_CURRENT_SOURCE_DIR}/Patches/leaf_more_selective_imports.patch CACHE INTERNAL "") + +# Fetch dependencies +FetchContent_Declare( + JUCE + GIT_REPOSITORY https://github.com/juce-framework/JUCE.git + GIT_TAG ${JUCE_VERSION} + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/JUCE +) + +FetchContent_Declare( + bungee + GIT_REPOSITORY https://github.com/bungee-audio-stretch/bungee.git + GIT_TAG ${BUNGEE_INSTALL_VERSION} + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/bungee + # We patch in our own maxPitchOctaves constant and lower the overly restrictive CMake version requirement + PATCH_COMMAND ${CMAKE_COMMAND} -DREPO_DIR= -DPATCH_FILE=${BUNGEE_PATCH_FILE} + -DNAME=bungee -P ${CMAKE_SOURCE_DIR}/Patches/apply_patch_if_needed.cmake +) + +FetchContent_Declare( + melatonin_blur + GIT_REPOSITORY https://github.com/sudara/melatonin_blur.git + GIT_TAG ${MELATONIN_BLUR_VERSION} + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/melatonin_blur +) + +FetchContent_Declare( + melatonin_inspector + GIT_REPOSITORY https://github.com/sudara/melatonin_inspector.git + GIT_TAG ${MELATONIN_INSPECTOR_VERSION} + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/melatonin_inspector +) + +FetchContent_Declare( + LEAF + GIT_REPOSITORY https://github.com/spiricom/LEAF.git + GIT_TAG ${LEAF_VERSION} + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/LEAF + # We patch leaf-config.h to allow including only the headers we need + # This patch is quite brittle and will likely fail on newer commits, but the pitch detection algorithm is stable + PATCH_COMMAND ${CMAKE_COMMAND} -DREPO_DIR= -DPATCH_FILE=${LEAF_PATCH_FILE} + -DNAME=LEAF -P ${CMAKE_SOURCE_DIR}/Patches/apply_patch_if_needed.cmake +) + +FetchContent_MakeAvailable(JUCE) +FetchContent_MakeAvailable(bungee) +FetchContent_MakeAvailable(melatonin_blur) +FetchContent_MakeAvailable(melatonin_inspector) +FetchContent_MakeAvailable(LEAF) + +# Bungee +set_target_properties(bungee_executable PROPERTIES EXCLUDE_FROM_ALL TRUE) +target_compile_definitions(bungee_library PRIVATE BUNGEE_MAX_OCTAVES=${STRETCHER_BUNGEE_MAX_OCTAVES}) + +# LEAF +set_source_files_properties( + ${leaf_SOURCE_DIR}/leaf/Src/leaf.c + ${leaf_SOURCE_DIR}/leaf/Src/leaf-analysis.c + ${leaf_SOURCE_DIR}/leaf/Src/leaf-mempool.c + PROPERTIES LANGUAGE CXX # LEAF uses some bad semantics that trip up the linker on GCC +) + +add_library(leaf STATIC + ${leaf_SOURCE_DIR}/leaf/Externals/d_fft_mayer.c + ${leaf_SOURCE_DIR}/leaf/Src/leaf.c + ${leaf_SOURCE_DIR}/leaf/Src/leaf-analysis.c + ${leaf_SOURCE_DIR}/leaf/Src/leaf-mempool.c +) + +target_include_directories(leaf SYSTEM PUBLIC + ${leaf_SOURCE_DIR}/leaf + ${leaf_SOURCE_DIR}/leaf/Inc +) + +target_compile_options(leaf PRIVATE + $<$:/W0> + $<$:-w> +) + +# External +add_library(external_deps STATIC + External/MTS/libMTSClient.cpp +) + +target_include_directories(external_deps SYSTEM PUBLIC + External/MTS + External/reaper-plugins + External/Gin + External/readerwriterqueue +) + +target_compile_options(external_deps PRIVATE + $<$:/W0> + $<$:-w> +) + +# Just a Sample plugin +juce_add_plugin(JustASample + PRODUCT_NAME "Just a Sample" + DESCRIPTION "Just a Sample is a modern, open-source audio sampler" + ICON_BIG "Assets/Icons/IconLogo.svg" + ICON_SMALL "Assets/Icons/IconLogo.svg" + COMPANY_NAME "Binyamin Friedman" + COMPANY_COPYRIGHT "Copyright (c) 2026 Binyamin Friedman" + COMPANY_WEBSITE "https://github.com/BOBONA/Just-a-Sample/" + BUNDLE_ID com.BinyaminFriedman.JustASample + PLUGIN_MANUFACTURER_CODE Manu + PLUGIN_CODE Bpf2 + FORMATS AU VST3 + VST3_CATEGORIES Sampler + IS_SYNTH TRUE + NEEDS_MIDI_INPUT TRUE + EDITOR_WANTS_KEYBOARD_FOCUS FALSE +) + +juce_generate_juce_header(JustASample) + +target_sources(JustASample PRIVATE + Source/CustomLookAndFeel.cpp + Source/CustomLookAndFeel.h + Source/PluginEditor.cpp + Source/PluginEditor.h + Source/PluginParameters.h + Source/PluginProcessor.cpp + Source/PluginProcessor.h + Source/Components/Buttons.h + Source/Components/FxChain.cpp + Source/Components/FxChain.h + Source/Components/FxDragTarget.h + Source/Components/FxModule.cpp + Source/Components/FxModule.h + Source/Components/InputDeviceSelector.cpp + Source/Components/InputDeviceSelector.h + Source/Components/Prompt.h + Source/Components/RangeSelector.h + Source/Components/SampleEditor.cpp + Source/Components/SampleEditor.h + Source/Components/SampleNavigator.cpp + Source/Components/SampleNavigator.h + Source/Components/Displays/ChorusVisualizer.cpp + Source/Components/Displays/ChorusVisualizer.h + Source/Components/Displays/DistortionVisualizer.cpp + Source/Components/Displays/DistortionVisualizer.h + Source/Components/Displays/FilterResponse.cpp + Source/Components/Displays/FilterResponse.h + Source/Components/Displays/ReverbResponse.cpp + Source/Components/Displays/ReverbResponse.h + Source/Components/Displays/SamplePainter.cpp + Source/Components/Displays/SamplePainter.h + Source/Sampler/CustomSamplerVoice.cpp + Source/Sampler/CustomSamplerVoice.h + Source/Sampler/CustomSynthesizer.h + Source/Sampler/SamplerParameters.cpp + Source/Sampler/SamplerParameters.h + Source/Sampler/Stretcher.h + Source/Sampler/Effects/BandEQ.h + Source/Sampler/Effects/Chorus.h + Source/Sampler/Effects/Distortion.h + Source/Sampler/Effects/Effect.h + Source/Sampler/Effects/Reverb.h + External/Gin/gin_distortion.h + External/Gin/gin_simpleverb.cpp + External/Gin/gin_simpleverb.h + Source/Utilities/BufferUtils.h + Source/Utilities/ComponentUtils.h + Source/Utilities/DeviceRecorder.h + Source/Utilities/ListenableValue.h + Source/Utilities/PitchDetector.h + Source/Utilities/SampleLoader.h + Source/Utilities/Reaper/ReaperVST3Extensions.cpp + Source/Utilities/Reaper/ReaperVST3Extensions.h +) + +juce_add_binary_data(AudioPluginData + SOURCES + Assets/Fonts/InriaSans-Bold.ttf + Assets/Fonts/InriaSans-Regular.ttf + Assets/Fonts/Inter-Bold.ttf + Assets/Fonts/Inter-Regular.ttf + Assets/Icons/IconAdd.svg + Assets/Icons/IconDarkMode.svg + Assets/Icons/IconDetect.svg + Assets/Icons/IconDrag.svg + Assets/Icons/IconFit.svg + Assets/Icons/IconHelp.svg + Assets/Icons/IconHideFX.svg + Assets/Icons/IconLightMode.svg + Assets/Icons/IconLinkEnabled.svg + Assets/Icons/IconLofi.svg + Assets/Icons/IconLogo.svg + Assets/Icons/IconLoop.svg + Assets/Icons/IconLoopStart.svg + Assets/Icons/IconMono.svg + Assets/Icons/IconPin.svg + Assets/Icons/IconPlay.svg + Assets/Icons/IconPreFX.svg + Assets/Icons/IconRecordSettings.svg + Assets/Icons/IconShowFX.svg + Assets/Icons/IconSpeed.svg + Assets/Icons/IconWaveformMode.svg + Assets/Icons/Logo.svg +) + +target_link_libraries(JustASample + PRIVATE + AudioPluginData + juce::juce_audio_utils + juce::juce_cryptography + juce::juce_dsp + juce::juce_opengl + external_deps + bungee_library + melatonin_blur + $<$,$>:melatonin_inspector> # Only link the inspector in debug + leaf + PUBLIC + juce::juce_recommended_config_flags + juce::juce_recommended_lto_flags + juce::juce_recommended_warning_flags +) + +target_include_directories(JustASample + PRIVATE + "${bungee_SOURCE_DIR}/bungee" + "${CMAKE_BINARY_DIR}" +) + +target_compile_definitions(JustASample PUBLIC + JUCE_WEB_BROWSER=0 + JUCE_USE_CURL=0 + JUCE_VST3_CAN_REPLACE_VST2=0 + JUCE_APP_VERSION="${APP_VERSION}" + DONT_SET_USING_JUCE_NAMESPACE=1 + BUNGEE_MAX_OCTAVES=${STRETCHER_BUNGEE_MAX_OCTAVES} + JAS_DARKMODE_DEFAULT=$ + JAS_VST3_REAPER_INTEGRATION=$ +) + +add_dependencies(JustASample GenerateBuildInfo) + +# JustASample itself already gets LTO via juce::juce_recommended_lto_flags +set_target_properties(bungee_library leaf PROPERTIES + INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE +) + +if (JAS_FAST_MATH) + if (MSVC) + foreach(_jas_target IN ITEMS JustASample bungee_library leaf) + target_compile_options(${_jas_target} PRIVATE $<$:/fp:fast>) + endforeach() + else() + foreach(_jas_target IN ITEMS JustASample bungee_library leaf) + target_compile_options(${_jas_target} PRIVATE $<$:-ffast-math>) + endforeach() + endif() +endif() + +if (JAS_ENABLE_AVX2) + if (MSVC) + foreach(_jas_target IN ITEMS JustASample bungee_library leaf) + target_compile_options(${_jas_target} PRIVATE $<$:/arch:AVX2>) + endforeach() + else() + foreach(_jas_target IN ITEMS JustASample bungee_library leaf) + target_compile_options(${_jas_target} PRIVATE $<$:-mavx2 -mfma>) + endforeach() + endif() +endif() + +# MSVC patching for Bungee +if (MSVC) + # Add a dummy unistd.h + target_include_directories(bungee_library PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/fake_headers) + file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/fake_headers/unistd.h "") + + # Use _USE_MATH_DEFINES to get M_PI and other math constants + target_compile_definitions(pffft PRIVATE _USE_MATH_DEFINES) + + target_compile_options(pffft PRIVATE "/fp:fast" "/GS-") + + if (NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # Add a header to define remove __attribute__, since MSVC doesn't support it + set(BUNGEE_COMPAT_H "${CMAKE_CURRENT_BINARY_DIR}/bungee_msvc_compat.h") + file(WRITE "${BUNGEE_COMPAT_H}" " +#ifndef BUNGEE_MSVC_COMPAT_H +#define BUNGEE_MSVC_COMPAT_H +#define __attribute__(x) +#endif +") + + target_compile_options(bungee_library PRIVATE "SHELL:/FI \"${BUNGEE_COMPAT_H}\"") + endif() +endif() diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..f0619ad --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,62 @@ +{ + "version": 3, + + "configurePresets": [ + { + "name": "windows", + "generator": "Ninja Multi-Config", + "binaryDir": "${sourceDir}/out/build/windows", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang-cl", + "CMAKE_CXX_COMPILER": "clang-cl", + "USE_CCACHE": "ON", + "JAS_VST3_REAPER_INTEGRATION": "true" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "macos", + "generator": "Xcode", + "binaryDir": "${sourceDir}/out/build/macos", + "cacheVariables": { + "CMAKE_OSX_ARCHITECTURES": "x86_64;arm64", + "CMAKE_OSX_DEPLOYMENT_TARGET": "10.13", + "JAS_ENABLE_AVX2": "OFF", + "USE_CCACHE": "ON" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + } + }, + { + "name": "linux", + "generator": "Ninja Multi-Config", + "binaryDir": "${sourceDir}/out/build/linux", + "cacheVariables": { + "USE_CCACHE": "ON" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + } + ], + + "buildPresets": [ + { "name": "debug-windows", "configurePreset": "windows", "configuration": "Debug", "targets": ["JustASample_VST3"] }, + { "name": "release-windows", "configurePreset": "windows", "configuration": "Release", "targets": ["JustASample_VST3"] }, + + { "name": "debug-linux", "configurePreset": "linux", "configuration": "Debug", "targets": ["JustASample_VST3"] }, + { "name": "release-linux", "configurePreset": "linux", "configuration": "Release", "targets": ["JustASample_VST3"] }, + + { "name": "debug-macos", "configurePreset": "macos", "configuration": "Debug", "targets": ["JustASample_VST3", "JustASample_AU"] }, + { "name": "release-macos", "configurePreset": "macos", "configuration": "Release", "targets": ["JustASample_VST3", "JustASample_AU"] } + ] +} diff --git a/External/Gin/gin_distortion.h b/External/Gin/gin_distortion.h new file mode 100644 index 0000000..8ca336d --- /dev/null +++ b/External/Gin/gin_distortion.h @@ -0,0 +1,237 @@ +/* + ============================================================================== + + This file is part of the GIN library. + Copyright (c) 2020 - Roland Rabien. + + MIT License + + Copyright (c) 2018 Chris Johnson + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION 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 + +namespace gin +{ + +//============================================================================== + /** Distortion based on AirWindows plugins + */ +class AirWindowsDistortion +{ +public: + AirWindowsDistortion() + { + reset(); + } + + void setSampleRate(double sr) + { + sampleRate = sr; + } + + void reset() + { + A = 0.2f; + B = 0.0f; + C = 1.0f; + D = 1.0f; + iirSampleAL = 0.0f; + iirSampleBL = 0.0f; + iirSampleAR = 0.0f; + iirSampleBR = 0.0f; + fpFlip = true; + fpNShapeL = 0.0f; + fpNShapeR = 0.0f; + } + + void setParams(float density, float highpass, float output, float mix) + { + A = density; + B = highpass; + C = output; + D = mix; + } + + void process(float* l, float* r, int sampleFrames) + { + double overallscale = 1.0; + overallscale /= 44100.0; + overallscale *= sampleRate; + double density = (A * 5.0) - 1.0; + double iirAmount = pow(B, 3) / overallscale; + double output = C; + double wet = D; + double dry = 1.0 - wet; + double bridgerectifier; + double out = fabs(density); + density = density * fabs(density); + double count; + + long double inputSampleL; + long double inputSampleR; + long double drySampleL; + long double drySampleR; + + while (--sampleFrames >= 0) + { + inputSampleL = *l; + inputSampleR = *r; + if (inputSampleL < 1.2e-38 && -inputSampleL < 1.2e-38) { + static int noisesource = 0; + //this declares a variable before anything else is compiled. It won't keep assigning + //it to 0 for every sample, it's as if the declaration doesn't exist in this context, + //but it lets me add this denormalization fix in a single place rather than updating + //it in three different locations. The variable isn't thread-safe but this is only + //a random seed and we can share it with whatever. + noisesource = noisesource % 1700021; noisesource++; + int residue = noisesource * noisesource; + residue = residue % 170003; residue *= residue; + residue = residue % 17011; residue *= residue; + residue = residue % 1709; residue *= residue; + residue = residue % 173; residue *= residue; + residue = residue % 17; + double applyresidue = residue; + applyresidue *= 0.00000001; + applyresidue *= 0.00000001; + inputSampleL = applyresidue; + } + if (inputSampleR < 1.2e-38 && -inputSampleR < 1.2e-38) { + static int noisesource = 0; + noisesource = noisesource % 1700021; noisesource++; + int residue = noisesource * noisesource; + residue = residue % 170003; residue *= residue; + residue = residue % 17011; residue *= residue; + residue = residue % 1709; residue *= residue; + residue = residue % 173; residue *= residue; + residue = residue % 17; + double applyresidue = residue; + applyresidue *= 0.00000001; + applyresidue *= 0.00000001; + inputSampleR = applyresidue; + //this denormalization routine produces a white noise at -300 dB which the noise + //shaping will interact with to produce a bipolar output, but the noise is actually + //all positive. That should stop any variables from going denormal, and the routine + //only kicks in if digital black is input. As a final touch, if you save to 24-bit + //the silence will return to being digital black again. + } + drySampleL = inputSampleL; + drySampleR = inputSampleR; + + if (fpFlip) + { + iirSampleAL = double((iirSampleAL * (1.0 - iirAmount)) + (inputSampleL * iirAmount)); + inputSampleL -= iirSampleAL; + iirSampleAR = double((iirSampleAR * (1.0 - iirAmount)) + (inputSampleR * iirAmount)); + inputSampleR -= iirSampleAR; + } + else + { + iirSampleBL = double((iirSampleBL * (1.0 - iirAmount)) + (inputSampleL * iirAmount)); + inputSampleL -= iirSampleBL; + iirSampleBR = double((iirSampleBR * (1.0 - iirAmount)) + (inputSampleR * iirAmount)); + inputSampleR -= iirSampleBR; + } + //highpass section + fpFlip = !fpFlip; + + count = density; + while (count > 1.0) + { + bridgerectifier = double(fabs(inputSampleL) * 1.57079633); + if (bridgerectifier > 1.57079633) bridgerectifier = 1.57079633; + //max value for sine function + bridgerectifier = sin(bridgerectifier); + if (inputSampleL > 0.0) inputSampleL = bridgerectifier; + else inputSampleL = -bridgerectifier; + + bridgerectifier = double(fabs(inputSampleR) * 1.57079633); + if (bridgerectifier > 1.57079633) bridgerectifier = 1.57079633; + //max value for sine function + bridgerectifier = sin(bridgerectifier); + if (inputSampleR > 0.0) inputSampleR = bridgerectifier; + else inputSampleR = -bridgerectifier; + + count = count - 1.0; + } + //we have now accounted for any really high density settings. + + while (out > 1.0) out = out - 1.0; + + bridgerectifier = double(fabs(inputSampleL) * 1.57079633); + if (bridgerectifier > 1.57079633) bridgerectifier = 1.57079633; + //max value for sine function + if (density > 0) bridgerectifier = sin(bridgerectifier); + else bridgerectifier = 1 - cos(bridgerectifier); + //produce either boosted or starved version + if (inputSampleL > 0) inputSampleL = (inputSampleL * (1 - out)) + (bridgerectifier * out); + else inputSampleL = (inputSampleL * (1 - out)) - (bridgerectifier * out); + //blend according to density control + + bridgerectifier = double(fabs(inputSampleR) * 1.57079633); + if (bridgerectifier > 1.57079633) bridgerectifier = 1.57079633; + //max value for sine function + if (density > 0) bridgerectifier = sin(bridgerectifier); + else bridgerectifier = 1 - cos(bridgerectifier); + //produce either boosted or starved version + if (inputSampleR > 0) inputSampleR = (inputSampleR * (1.0 - out)) + (bridgerectifier * out); + else inputSampleR = (inputSampleR * (1.0 - out)) - (bridgerectifier * out); + //blend according to density control + + if (output < 1.0) { + inputSampleL *= output; + inputSampleR *= output; + } + if (wet < 1.0) { + inputSampleL = (drySampleL * dry) + (inputSampleL * wet); + inputSampleR = (drySampleR * dry) + (inputSampleR * wet); + } + //nice little output stage template: if we have another scale of floating point + //number, we really don't want to meaninglessly multiply that by 1.0. + + //stereo 32 bit dither, made small and tidy. + int expon; frexpf((float)inputSampleL, &expon); + long double dither = (rand() / (RAND_MAX * 7.737125245533627e+25)) * pow(2, expon + 62); + inputSampleL += (dither - fpNShapeL); fpNShapeL = dither; + frexpf((float)inputSampleR, &expon); + dither = (rand() / (RAND_MAX * 7.737125245533627e+25)) * pow(2, expon + 62); + inputSampleR += (dither - fpNShapeR); fpNShapeR = dither; + //end 32 bit dither + + *l = float(inputSampleL); + *r = float(inputSampleR); + + l++; + r++; + } + } + +private: + double sampleRate = 44100.0; + long double fpNShapeL, fpNShapeR; + double iirSampleAL, iirSampleBL, iirSampleAR, iirSampleBR; + bool fpFlip; + float A, B, C, D; +}; +} // namespace gin \ No newline at end of file diff --git a/External/Gin/gin_simpleverb.cpp b/External/Gin/gin_simpleverb.cpp new file mode 100644 index 0000000..faa89cd --- /dev/null +++ b/External/Gin/gin_simpleverb.cpp @@ -0,0 +1,328 @@ +/* + ============================================================================== + + This file is part of the GIN library. + Copyright (c) 2019 - Roland Rabien. + + ============================================================================== + */ + +#include "gin_simpleverb.h" + +gin::SimpleVerb::SimpleVerb() +{ + roomSizeFader = 0.5; + roomSize = 55; + + preDelayFader = 0; + + preDelayLength = 0; + preDelayPos = 0; + + dampFader = 0.5; + damp = 0.25; + + freqLPFader = 1; + freqHPFader = 0; + + freqLP = 24000; + freqHP = 0; + + b1LP = -std::exp(-2.0f * juce::MathConstants::pi * freqLP / sampleRate); // 100Hz + a0LP = 1.0f + b1LP; + + b1HP = -std::exp(-2.0f * juce::MathConstants::pi * freqHP / sampleRate); // 100Hz + a0HP = 1.0f + b1HP; + + dry = 1; + wet = 0.5; + + setSampleRate(44100); +} + +void gin::SimpleVerb::setSampleRate(float sr) +{ + constexpr float roomMaxSize = 100.0f; + + sampleRate = sr; + + auto comb1MaxLength = static_cast(C1 * roomMaxSize * sampleRate / 1000); + comb1.resize(comb1MaxLength); + auto comb2MaxLength = static_cast(C2 * roomMaxSize * sampleRate / 1000); + comb2.resize(comb2MaxLength); + auto comb3MaxLength = static_cast(C3 * roomMaxSize * sampleRate / 1000); + comb3.resize(comb3MaxLength); + auto comb4MaxLength = static_cast(C4 * roomMaxSize * sampleRate / 1000); + comb4.resize(comb4MaxLength); + auto comb5MaxLength = static_cast(C5 * roomMaxSize * sampleRate / 1000); + comb5.resize(comb5MaxLength); + auto comb6MaxLength = static_cast(C6 * roomMaxSize * sampleRate / 1000); + comb6.resize(comb6MaxLength); + auto comb7MaxLength = static_cast(C7 * roomMaxSize * sampleRate / 1000); + comb7.resize(comb7MaxLength); + auto comb8MaxLength = static_cast(C8 * roomMaxSize * sampleRate / 1000); + comb8.resize(comb8MaxLength); + auto comb9MaxLength = static_cast(C9 * roomMaxSize * sampleRate / 1000); + comb9.resize(comb9MaxLength); + auto comb10MaxLength = static_cast(C10 * roomMaxSize * sampleRate / 1000); + comb10.resize(comb10MaxLength); + auto comb11MaxLength = static_cast(C11 * roomMaxSize * sampleRate / 1000); + comb11.resize(comb11MaxLength); + auto comb12MaxLength = static_cast(C12 * roomMaxSize * sampleRate / 1000); + comb12.resize(comb12MaxLength); + + allpassL1Length = static_cast(AL1 * sampleRate / 1000); + allpassL1.resize(allpassL1Length); + allpassL2Length = static_cast((AL2 + SW) * sampleRate / 1000); + allpassL2.resize(allpassL2Length); + allpassL3Length = static_cast(AL3 * sampleRate / 1000); + allpassL3.resize(allpassL3Length); + + allpassR1Length = static_cast((AR1 + SW) * sampleRate / 1000); + allpassR1.resize(allpassR1Length); + allpassR2Length = static_cast(AR2 * sampleRate / 1000); + allpassR2.resize(allpassR2Length); + allpassR3Length = static_cast((AR3 + SW) * sampleRate / 1000); + allpassR3.resize(allpassR3Length); + + auto preDelayMaxLength = static_cast(500 * sampleRate / 1000); + preDelay.resize(preDelayMaxLength); + + flushPreDelay(); + flushBuffers(); + + allpassL1Pos = allpassL2Pos = allpassL3Pos = allpassR1Pos = allpassR2Pos = allpassR3Pos = 0; + tmp1LP = tmp2LP = tmp1HP = tmp2HP = 0; + + comb1Pos = comb2Pos = comb3Pos = comb4Pos = comb5Pos = comb6Pos = comb7Pos = comb8Pos = 0; + comb9Pos = comb10Pos = comb11Pos = comb12Pos = 0; + preDelayPos = 0; +} + +void gin::SimpleVerb::flushPreDelay() +{ + std::fill(preDelay.begin(), preDelay.end(), 0.0f); +} + +void gin::SimpleVerb::flushBuffers() +{ + std::fill(comb1.begin(), comb1.end(), 0.0f); + std::fill(comb2.begin(), comb2.end(), 0.0f); + std::fill(comb3.begin(), comb3.end(), 0.0f); + std::fill(comb4.begin(), comb4.end(), 0.0f); + std::fill(comb5.begin(), comb5.end(), 0.0f); + std::fill(comb6.begin(), comb6.end(), 0.0f); + std::fill(comb7.begin(), comb7.end(), 0.0f); + std::fill(comb8.begin(), comb8.end(), 0.0f); + std::fill(comb9.begin(), comb9.end(), 0.0f); + std::fill(comb10.begin(), comb10.end(), 0.0f); + std::fill(comb11.begin(), comb11.end(), 0.0f); + std::fill(comb12.begin(), comb12.end(), 0.0f); + std::fill(allpassL1.begin(), allpassL1.end(), 0.0f); + std::fill(allpassL2.begin(), allpassL2.end(), 0.0f); + std::fill(allpassL3.begin(), allpassL3.end(), 0.0f); + std::fill(allpassR1.begin(), allpassR1.end(), 0.0f); + std::fill(allpassR2.begin(), allpassR2.end(), 0.0f); + std::fill(allpassR3.begin(), allpassR3.end(), 0.0f); +} + +void gin::SimpleVerb::setParameters(float roomIn, float dampIn, float preDelayIn, float lpFaderIn, float hpFaderIn, float wetIn, float dryIn) +{ + if (!juce::approximatelyEqual(roomIn, roomSizeFader)) + { + roomSizeFader = roomIn; + roomSize = 5 + roomSizeFader * roomSizeFader * 95; + + comb1Length = static_cast(C1 * roomSize * sampleRate / 1000); + comb1Pos = 0; + + comb2Length = static_cast(C2 * roomSize * sampleRate / 1000); + comb2Pos = 0; + + comb3Length = static_cast(C3 * roomSize * sampleRate / 1000); + comb3Pos = 0; + + comb4Length = static_cast(C4 * roomSize * sampleRate / 1000); + comb4Pos = 0; + + comb5Length = static_cast(C5 * roomSize * sampleRate / 1000); + comb5Pos = 0; + + comb6Length = static_cast(C6 * roomSize * sampleRate / 1000); + comb6Pos = 0; + + comb7Length = static_cast(C7 * roomSize * sampleRate / 1000); + comb7Pos = 0; + + comb8Length = static_cast(C8 * roomSize * sampleRate / 1000); + comb8Pos = 0; + + comb9Length = static_cast(C9 * roomSize * sampleRate / 1000); + comb9Pos = 0; + + comb10Length = static_cast(C10 * roomSize * sampleRate / 1000); + comb10Pos = 0; + + comb11Length = static_cast(C11 * roomSize * sampleRate / 1000); + comb11Pos = 0; + + comb12Length = static_cast(C12 * roomSize * sampleRate / 1000); + comb12Pos = 0; + + flushBuffers(); + } + if (!juce::approximatelyEqual(dampIn, dampFader)) + { + dampFader = dampIn; + damp = std::min(1.0f - dampFader * dampFader, 0.95f); + } + if (!juce::approximatelyEqual(preDelayIn, preDelayFader)) + { + preDelayFader = preDelayIn; + preDelayLength = static_cast(preDelayFader * preDelayFader * 250 * sampleRate / 1000); + preDelayPos = 0; + flushPreDelay(); + } + if (!juce::approximatelyEqual(lpFaderIn, freqLPFader)) + { + freqLPFader = lpFaderIn; + freqLP = freqLPFader * freqLPFader * freqLPFader * 24000; + b1LP = -std::exp(-2.0f * juce::MathConstants::pi * freqLP / sampleRate); // 100Hz + a0LP = 1.0f + b1LP; + } + if (!juce::approximatelyEqual(hpFaderIn, freqHPFader)) + { + freqHPFader = hpFaderIn; + freqHP = freqHPFader * freqHPFader * freqHPFader * 24000; + b1HP = -std::exp(-2.0f * juce::MathConstants::pi * freqHP / sampleRate); // 100Hz + a0HP = 1.0f + b1HP; + } + if (!juce::approximatelyEqual(dryIn, dryFader)) + { + dryFader = dryIn; + dry = dryFader * 2; + } + if (!juce::approximatelyEqual(wetIn, wetFader)) + { + wetFader = wetIn; + wet = wetFader * 2; + } +} + +void gin::SimpleVerb::process(const float* in1, const float* in2, float* out1, float* out2, int numSamples) +{ + int sampleFrames = numSamples; + + while (--sampleFrames >= 0) + { + if (preDelayLength <= 1) + { + reverb = ((*in1) + (*in2)) / (1 + damp) + cDC_; + } + else + { + preDelay[preDelayPos] = ((*in1) + (*in2)) / (1 + damp) + cDC_; + if (++preDelayPos >= preDelayLength) + preDelayPos = 0; + + reverb = preDelay[preDelayPos]; + } + + comb1[comb1Pos] = reverb * 0.49f + comb1[comb1Pos] * damp; + comb2[comb2Pos] = reverb * 0.76f + comb2[comb2Pos] * damp; + comb3[comb3Pos] = reverb * 1.00f + comb3[comb3Pos] * damp; + comb4[comb4Pos] = reverb * 0.91f + comb4[comb4Pos] * damp; + comb5[comb5Pos] = reverb * 0.79f + comb5[comb5Pos] * damp; + comb6[comb6Pos] = reverb * 0.71f + comb6[comb6Pos] * damp; + comb7[comb7Pos] = reverb * 0.59f + comb7[comb7Pos] * damp; + comb8[comb8Pos] = reverb * 0.51f + comb8[comb8Pos] * damp; + comb9[comb9Pos] = reverb * 0.42f + comb9[comb9Pos] * damp; + comb10[comb10Pos] = reverb * 0.38f + comb10[comb10Pos] * damp; + comb11[comb11Pos] = reverb * 0.35f + comb11[comb11Pos] * damp; + comb12[comb12Pos] = reverb * 0.30f + comb12[comb12Pos] * damp; + + if (++comb1Pos >= comb1Length) comb1Pos = 0; + if (++comb2Pos >= comb2Length) comb2Pos = 0; + if (++comb3Pos >= comb3Length) comb3Pos = 0; + if (++comb4Pos >= comb4Length) comb4Pos = 0; + if (++comb5Pos >= comb5Length) comb5Pos = 0; + if (++comb6Pos >= comb6Length) comb6Pos = 0; + if (++comb7Pos >= comb7Length) comb7Pos = 0; + if (++comb8Pos >= comb8Length) comb8Pos = 0; + if (++comb9Pos >= comb9Length) comb9Pos = 0; + if (++comb10Pos >= comb10Length) comb10Pos = 0; + if (++comb11Pos >= comb11Length) comb11Pos = 0; + if (++comb12Pos >= comb12Length) comb12Pos = 0; + + reverb = (comb1[comb1Pos] + + comb2[comb2Pos] + + comb3[comb3Pos] + + comb4[comb4Pos] + + comb5[comb5Pos] + + comb6[comb6Pos] + + comb7[comb7Pos] + + comb8[comb8Pos] + + comb9[comb9Pos] + + comb10[comb10Pos] + + comb11[comb11Pos] + + comb12[comb12Pos]); + + jassert(!std::isnan(reverb) && !std::isinf(reverb)); + + allpassL1[allpassL1Pos] = reverb + allpassL1[allpassL1Pos] * AP1FBQ; + left = (reverb - allpassL1[allpassL1Pos] * AP1FBQ); + jassert(!std::isnan(left) && !std::isinf(left)); + if (++allpassL1Pos >= allpassL1Length) + allpassL1Pos = 0; + + allpassL2[allpassL2Pos] = left + allpassL2[allpassL2Pos] * AP2FBQ; + left = (left - allpassL2[allpassL2Pos] * AP2FBQ); + jassert(!std::isnan(left) && !std::isinf(left)); + if (++allpassL2Pos >= allpassL2Length) + allpassL2Pos = 0; + + allpassL3[allpassL3Pos] = left + allpassL3[allpassL3Pos] * AP3FBQ; + left = (left - allpassL3[allpassL3Pos] * AP3FBQ); + jassert(!std::isnan(left) && !std::isinf(left)); + if (++allpassL3Pos >= allpassL3Length) + allpassL3Pos = 0; + + allpassR1[allpassR1Pos] = reverb + allpassR1[allpassR1Pos] * AP1FBQ; + right = (reverb - allpassR1[allpassR1Pos] * AP1FBQ); + jassert(!std::isnan(right) && !std::isinf(right)); + if (++allpassR1Pos >= allpassR1Length) + allpassR1Pos = 0; + + allpassR2[allpassR2Pos] = right + allpassR2[allpassR2Pos] * AP2FBQ; + right = (right - allpassR2[allpassR2Pos] * AP2FBQ); + jassert(!std::isnan(right) && !std::isinf(right)); + if (++allpassR2Pos >= allpassR2Length) + allpassR2Pos = 0; + + allpassR3[allpassR3Pos] = right + allpassR3[allpassR3Pos] * AP3FBQ; + right = (right - allpassR3[allpassR3Pos] * AP3FBQ); + jassert(!std::isnan(right) && !std::isinf(right)); + if (++allpassR3Pos >= allpassR3Length) + allpassR3Pos = 0; + + if (!juce::approximatelyEqual(freqHPFader, 0.0f)) + { + left -= (tmp1HP = a0HP * left - b1HP * tmp1HP + cDC_) - cDC_; + right -= (tmp2HP = a0HP * right - b1HP * tmp2HP + cDC_) - cDC_; + + jassert(!std::isnan(left) && !std::isinf(left)); + jassert(!std::isnan(right) && !std::isinf(right)); + } + if (!juce::approximatelyEqual(freqLPFader, 1.0f)) + { + left = (tmp1LP = a0LP * left - b1LP * tmp1LP + cDC_) - cDC_; + right = (tmp2LP = a0LP * right - b1LP * tmp2LP + cDC_) - cDC_; + + jassert(!std::isnan(left) && !std::isinf(left)); + jassert(!std::isnan(right) && !std::isinf(right)); + } + + (*out1++) = (*in1++) * dry + left * wet; + (*out2++) = (*in2++) * dry + right * wet; + } +} diff --git a/External/Gin/gin_simpleverb.h b/External/Gin/gin_simpleverb.h new file mode 100644 index 0000000..4a48669 --- /dev/null +++ b/External/Gin/gin_simpleverb.h @@ -0,0 +1,134 @@ +/* + ============================================================================== + + This file is part of the GIN library. + Copyright (c) 2019 - Roland Rabien. + + ============================================================================== + */ + + +#pragma once +#include + +namespace gin +{ + + /** Simple Reverb + + Copyright (c) 2006-2008 and 2012, Michael "LOSER" Gruhn + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY, FITNESS AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + */ + +class SimpleVerb +{ +public: + SimpleVerb(); + + void setSampleRate(float sr); + void process(const float* in1, const float* in2, float* out1, float* out2, int numSamples); + void setParameters(float roomIn, float dampIn, float preDelayIn, float lpFaderIn, float hpFaderIn, float wetIn, float dryIn); + +private: + void flushPreDelay(); + void flushBuffers(); + + unsigned int preDelayPos, preDelayLength; + + std::vector preDelay; + float preDelayFader; + + unsigned int comb1Pos, comb1Length; + std::vector comb1; + unsigned int comb2Pos, comb2Length; + std::vector comb2; + unsigned int comb3Pos, comb3Length; + std::vector comb3; + unsigned int comb4Pos, comb4Length; + std::vector comb4; + unsigned int comb5Pos, comb5Length; + std::vector comb5; + unsigned int comb6Pos, comb6Length; + std::vector comb6; + unsigned int comb7Pos, comb7Length; + std::vector comb7; + unsigned int comb8Pos, comb8Length; + std::vector comb8; + unsigned int comb9Pos, comb9Length; + std::vector comb9; + unsigned int comb10Pos, comb10Length; + std::vector comb10; + unsigned int comb11Pos, comb11Length; + std::vector comb11; + unsigned int comb12Pos, comb12Length; + std::vector comb12; + + unsigned int allpassL1Pos, allpassL1Length; + std::vector allpassL1; + unsigned int allpassL2Pos, allpassL2Length; + std::vector allpassL2; + unsigned int allpassL3Pos, allpassL3Length; + std::vector allpassL3; + + unsigned int allpassR1Pos, allpassR1Length; + std::vector allpassR1; + unsigned int allpassR2Pos, allpassR2Length; + std::vector allpassR2; + unsigned int allpassR3Pos, allpassR3Length; + std::vector allpassR3; + + float reverb, damp, dry, wet, left, right; + float roomSize = -1.0f; + float roomSizeFader = -1.0f; + float dampFader = -1.0f; + float dryFader = -1.0f; + float wetFader = -1.0f; + float sampleRate = 44100.0f; + + float freqLP, freqLPFader; + float freqHP, freqHPFader; + + float a0LP, b1LP, tmp1LP, tmp2LP; + float a0HP, b1HP, tmp1HP, tmp2HP; + + static constexpr float cDC_ = 1e-30f; + + static constexpr float C1 = 1.00f; + static constexpr float C2 = 1.09f; + static constexpr float C3 = 1.16f; + static constexpr float C4 = 1.23f; + static constexpr float C5 = 1.32f; + static constexpr float C6 = 1.41f; + static constexpr float C7 = 1.45f; + static constexpr float C8 = 1.56f; + static constexpr float C9 = 1.66f; + static constexpr float C10 = 1.71f; + static constexpr float C11 = 1.80f; + static constexpr float C12 = 1.90f; + + static constexpr float AL1 = 1.0f; + static constexpr float AL2 = 2.5f; + static constexpr float AL3 = 5.0f; + static constexpr float AR1 = 1.0f; + static constexpr float AR2 = 2.5f; + static constexpr float AR3 = 5.0f; + + static constexpr float SW = 1.0f; + + static constexpr float AP1FBQ = 0.6f; + static constexpr float AP2FBQ = 0.6f; + static constexpr float AP3FBQ = 0.6f; +}; +} // namespace gin diff --git a/External/MTS/libMTSClient.cpp b/External/MTS/libMTSClient.cpp new file mode 100644 index 0000000..f60a04a --- /dev/null +++ b/External/MTS/libMTSClient.cpp @@ -0,0 +1,958 @@ +/* +Copyright (C) 2021 by ODDSound Ltd. info@oddsound.com + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. +*/ + +#include "libMTSClient.h" +#include +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(__TOS_WIN__) || defined(_MSC_VER) +#define MTS_ESP_WIN +#define WIN32_LEAN_AND_MEAN +#include +typedef HRESULT (WINAPI* SHGetKnownFolderPathFunc) (const GUID*, DWORD, HANDLE, PWSTR*); +typedef void (WINAPI* CoTaskMemFreeFunc) (LPVOID); +#else +#include +#endif + +const static int libMTSVersion = 0x00010003; + +const static double ln2 = 0.693147180559945309417; +const static double ratioToSemitones = 17.31234049066756088832; // 12.0 / log(2.0) + +typedef void (*mts_void__void)(void); +typedef bool (*mts_bool__void)(void); +typedef int (*mts_int__void)(void); +typedef bool (*mts_bool__char_char)(char, char); +typedef const double *(*mts_pConstDouble__void)(void); +typedef const double *(*mts_pConstDouble__char)(char); +typedef bool (*mts_bool__char)(char); +typedef const char *(*mts_pConstChar__void)(void); +typedef double (*mts_double__void)(void); +typedef char (*mts_char__void)(void); + +struct mtsclientglobal +{ + mtsclientglobal() + : RegisterClient(0) + , DeregisterClient(0) + , HasMaster(0) + , GetVersionNumber(0) + , ShouldFilterNote(0) + , ShouldFilterNoteMultiChannel(0) + , GetTuning(0) + , GetMultiChannelTuning(0) + , UseMultiChannelTuning(0) + , GetScaleName(0) + , GetPeriodRatio(0) + , GetMapSize(0) + , GetMapStartKey(0) + , GetRefKey(0) + , esp_retuning(0) + , handle(0) + { + for (int i = 0; i < 128; i++) + iet[i] = 1. / (440.0 * pow(2.0, (i - 69.0) / 12.0)); + + load_lib(); + + if (GetTuning) + esp_retuning = GetTuning(); + + for (int i = 0; i < 16; i++) + multi_channel_esp_retuning[i] = GetMultiChannelTuning ? GetMultiChannelTuning(static_cast(i)) : 0; + } + + inline bool isOnline() const {return esp_retuning && HasMaster && HasMaster();} + + // interface to lib + mts_void__void RegisterClient; + mts_void__void DeregisterClient; + mts_bool__void HasMaster; + mts_int__void GetVersionNumber; + mts_bool__char_char ShouldFilterNote; + mts_bool__char_char ShouldFilterNoteMultiChannel; + mts_pConstDouble__void GetTuning; + mts_pConstDouble__char GetMultiChannelTuning; + mts_bool__char UseMultiChannelTuning; + mts_pConstChar__void GetScaleName; + mts_double__void GetPeriodRatio; + mts_char__void GetMapSize; + mts_char__void GetMapStartKey; + mts_char__void GetRefKey; + + // tuning tables + double iet[128]; + const double *esp_retuning; + const double *multi_channel_esp_retuning[16]; + +#ifdef MTS_ESP_WIN + void load_lib() + { + SHGetKnownFolderPathFunc SHGetKnownFolderPath = 0; + CoTaskMemFreeFunc CoTaskMemFree = 0; + + HMODULE shell32Module = GetModuleHandleW(L"Shell32.dll"); + HMODULE ole32Module = GetModuleHandleW(L"Ole32.dll"); + + if (shell32Module) + SHGetKnownFolderPath = (SHGetKnownFolderPathFunc)GetProcAddress(shell32Module, "SHGetKnownFolderPath"); + + if (ole32Module) + CoTaskMemFree = (CoTaskMemFreeFunc)GetProcAddress(ole32Module, "CoTaskMemFree"); + + if (SHGetKnownFolderPath && CoTaskMemFree) + { + const GUID FOLDERID_ProgramFilesCommonGUID = {0xF7F1ED05, 0x9F6D, 0x47A2, 0xAA, 0xAE, 0x29, 0xD3, 0x17, 0xC6, 0xF0, 0x66}; + PWSTR cf = NULL; + if (SHGetKnownFolderPath(&FOLDERID_ProgramFilesCommonGUID, 0, 0, &cf) >= 0) + { + WCHAR buffer[MAX_PATH]; + buffer[0] = L'\0'; + if (cf) + wcsncpy(buffer, cf, MAX_PATH); + CoTaskMemFree(cf); + buffer[MAX_PATH - 1] = L'\0'; + const WCHAR *libpath = L"\\MTS-ESP\\LIBMTS.dll"; + DWORD cfLen = wcslen(buffer); + wcsncat(buffer, libpath, MAX_PATH - cfLen - 1); + handle = LoadLibraryW(buffer); + if (!handle) + return; + } + else + { + CoTaskMemFree(cf); + return; + } + } + else + { + return; + } + + RegisterClient = (mts_void__void) GetProcAddress(handle, "MTS_RegisterClient"); + DeregisterClient = (mts_void__void) GetProcAddress(handle, "MTS_DeregisterClient"); + HasMaster = (mts_bool__void) GetProcAddress(handle, "MTS_HasMaster"); + GetVersionNumber = (mts_int__void) GetProcAddress(handle, "MTS_GetVersionNumber"); + ShouldFilterNote = (mts_bool__char_char) GetProcAddress(handle, "MTS_ShouldFilterNote"); + ShouldFilterNoteMultiChannel = (mts_bool__char_char) GetProcAddress(handle, "MTS_ShouldFilterNoteMultiChannel"); + GetTuning = (mts_pConstDouble__void) GetProcAddress(handle, "MTS_GetTuningTable"); + GetMultiChannelTuning = (mts_pConstDouble__char) GetProcAddress(handle, "MTS_GetMultiChannelTuningTable"); + UseMultiChannelTuning = (mts_bool__char) GetProcAddress(handle, "MTS_UseMultiChannelTuning"); + GetScaleName = (mts_pConstChar__void) GetProcAddress(handle, "MTS_GetScaleName"); + GetPeriodRatio = (mts_double__void) GetProcAddress(handle, "MTS_GetPeriodRatio"); + GetMapSize = (mts_char__void) GetProcAddress(handle, "MTS_GetMapSize"); + GetMapStartKey = (mts_char__void) GetProcAddress(handle, "MTS_GetMapStartKey"); + GetRefKey = (mts_char__void) GetProcAddress(handle, "MTS_GetRefKey"); + } + + ~mtsclientglobal() + { + if (handle) + FreeLibrary(handle); + } + + HINSTANCE handle; +#else + void load_lib() + { + if (!(handle = dlopen("/Library/Application Support/MTS-ESP/libMTS.dylib", RTLD_NOW)) && + !(handle = dlopen("/usr/local/lib/libMTS.so", RTLD_NOW))) + { + return; + } + + RegisterClient = (mts_void__void) dlsym(handle, "MTS_RegisterClient"); + DeregisterClient = (mts_void__void) dlsym(handle, "MTS_DeregisterClient"); + HasMaster = (mts_bool__void) dlsym(handle, "MTS_HasMaster"); + GetVersionNumber = (mts_int__void) dlsym(handle, "MTS_GetVersionNumber"); + ShouldFilterNote = (mts_bool__char_char) dlsym(handle, "MTS_ShouldFilterNote"); + ShouldFilterNoteMultiChannel = (mts_bool__char_char) dlsym(handle, "MTS_ShouldFilterNoteMultiChannel"); + GetTuning = (mts_pConstDouble__void) dlsym(handle, "MTS_GetTuningTable"); + GetMultiChannelTuning = (mts_pConstDouble__char) dlsym(handle, "MTS_GetMultiChannelTuningTable"); + UseMultiChannelTuning = (mts_bool__char) dlsym(handle, "MTS_UseMultiChannelTuning"); + GetScaleName = (mts_pConstChar__void) dlsym(handle, "MTS_GetScaleName"); + GetPeriodRatio = (mts_double__void) dlsym(handle, "MTS_GetPeriodRatio"); + GetMapSize = (mts_char__void) dlsym(handle, "MTS_GetMapSize"); + GetMapStartKey = (mts_char__void) dlsym(handle, "MTS_GetMapStartKey"); + GetRefKey = (mts_char__void) dlsym(handle, "MTS_GetRefKey"); + } + + ~mtsclientglobal() + { + if (handle) + dlclose(handle); + } + + void *handle; +#endif +}; + +static mtsclientglobal global; + +struct MTSClient +{ + struct Tuning + { + enum {eRatioValid = 1, eSemitonesValid = 1 << 1}; + int flags; + double freq; // always valid + double ratio; + double semitones; + }; + + MTSClient() + : tuningName("12-TET") + , periodRatioLocal(2.0) + , periodSemitones(12.0) + , mapSizeLocal(static_cast(-1)) + , mapStartKeyLocal(static_cast(-1)) + , supportsNoteFiltering(false) + , supportsMultiChannelNoteFiltering(false) + , supportsMultiChannelTuning(false) + , freqRequestReceived(false) + , receivedMTSSysEx(false) + { + for (int i = 0; i < 128; i++) + { + localFreqs[i] = 440.0 * pow(2.0, (i - 69.0) / 12.0); + localTunings[i].flags = 0; + localTunings[i].freq = localFreqs[i]; + globalTunings[i].flags = 0; + globalTunings[i].freq = localFreqs[i]; + } + + for (int i = 0; i < 16; i++) + { + for (int j = 0; j < 128; j++) + { + globalMultichannelTunings[i][j].flags = 0; + globalMultichannelTunings[i][j].freq = localFreqs[i]; + } + } + + if (global.RegisterClient) + global.RegisterClient(); + } + + ~MTSClient() + { + if (global.DeregisterClient) + global.DeregisterClient(); + } + + inline bool hasMaster() {return global.isOnline();} + inline bool shouldUpdateLibrary() {return global.GetVersionNumber ? (global.GetVersionNumber() < libMTSVersion) : false;} + + inline double freq(char midinote, char midichannel) + { + int note = midinote & 127; + int channel = midichannel & 15; + + freqRequestReceived = true; + supportsMultiChannelTuning = !(midichannel & ~15); + + if (!global.isOnline()) + return localTunings[note].freq; + + if ((!supportsNoteFiltering || supportsMultiChannelNoteFiltering) && + supportsMultiChannelTuning && + global.UseMultiChannelTuning && + global.UseMultiChannelTuning(midichannel) && + global.multi_channel_esp_retuning[channel]) + { + globalMultichannelTunings[channel][note].freq = global.multi_channel_esp_retuning[channel][note]; + globalMultichannelTunings[channel][note].flags = 0; + return globalMultichannelTunings[channel][note].freq; + } + + globalTunings[note].freq = global.esp_retuning[note]; + globalTunings[note].flags = 0; + return globalTunings[note].freq; + } + + inline double ratio(char midinote, char midichannel) + { + int note = midinote & 127; + int channel = midichannel & 15; + + freqRequestReceived = true; + supportsMultiChannelTuning = !(midichannel & ~15); + + if (!global.isOnline()) + { + if (!receivedMTSSysEx) + return 1.0; + + if (localTunings[note].flags & Tuning::eRatioValid) + return localTunings[note].ratio; + + localTunings[note].ratio = localTunings[note].freq * global.iet[note]; + localTunings[note].flags |= Tuning::eRatioValid; + return localTunings[note].ratio; + } + + if ((!supportsNoteFiltering || supportsMultiChannelNoteFiltering) && + supportsMultiChannelTuning && + global.UseMultiChannelTuning && + global.UseMultiChannelTuning(midichannel) && + global.multi_channel_esp_retuning[channel]) + { + double freq = global.multi_channel_esp_retuning[channel][note]; + + if (globalMultichannelTunings[channel][note].freq == freq && + (globalMultichannelTunings[channel][note].flags & Tuning::eRatioValid)) + { + return globalMultichannelTunings[channel][note].ratio; + } + + globalMultichannelTunings[channel][note].freq = global.multi_channel_esp_retuning[channel][note]; + globalMultichannelTunings[channel][note].ratio = globalMultichannelTunings[channel][note].freq * global.iet[note]; + globalMultichannelTunings[channel][note].flags = Tuning::eRatioValid; + return globalMultichannelTunings[channel][note].ratio; + } + + double freq = global.esp_retuning[note]; + + if (globalTunings[note].freq == freq && + (globalTunings[note].flags & Tuning::eRatioValid)) + { + return globalTunings[note].ratio; + } + + globalTunings[note].freq = global.esp_retuning[note]; + globalTunings[note].ratio = globalTunings[note].freq * global.iet[note]; + globalTunings[note].flags = Tuning::eRatioValid; + return globalTunings[note].ratio; + } + + inline double semitones(char midinote, char midichannel) + { + int note = midinote & 127; + int channel = midichannel & 15; + + freqRequestReceived = true; + supportsMultiChannelTuning = !(midichannel & ~15); + + if (!global.isOnline()) + { + if (!receivedMTSSysEx) + return 0.0; + + if (localTunings[note].flags & Tuning::eSemitonesValid) + return localTunings[note].semitones; + + if (localTunings[note].flags & Tuning::eRatioValid) + { + localTunings[note].semitones = ratioToSemitones * log(localTunings[note].ratio); + localTunings[note].flags |= Tuning::eSemitonesValid; + return localTunings[note].semitones; + } + + localTunings[note].ratio = localTunings[note].freq * global.iet[note]; + localTunings[note].semitones = ratioToSemitones * log(localTunings[note].ratio); + localTunings[note].flags |= Tuning::eRatioValid | Tuning::eSemitonesValid; + return localTunings[note].semitones; + } + + if ((!supportsNoteFiltering || supportsMultiChannelNoteFiltering) && + supportsMultiChannelTuning && + global.UseMultiChannelTuning && + global.UseMultiChannelTuning(midichannel) && + global.multi_channel_esp_retuning[channel]) + { + double freq = global.multi_channel_esp_retuning[channel][note]; + + if (globalMultichannelTunings[channel][note].freq == freq) + { + if (globalMultichannelTunings[channel][note].flags & Tuning::eSemitonesValid) + return globalMultichannelTunings[channel][note].semitones; + + if (globalMultichannelTunings[channel][note].flags & Tuning::eRatioValid) + { + globalMultichannelTunings[channel][note].semitones = ratioToSemitones * log(globalMultichannelTunings[channel][note].ratio); + globalMultichannelTunings[channel][note].flags |= Tuning::eSemitonesValid; + return globalMultichannelTunings[channel][note].semitones; + } + } + + globalMultichannelTunings[channel][note].freq = freq; + globalMultichannelTunings[channel][note].ratio = freq * global.iet[note]; + globalMultichannelTunings[channel][note].semitones = ratioToSemitones * log(globalMultichannelTunings[channel][note].ratio); + globalMultichannelTunings[channel][note].flags = Tuning::eRatioValid | Tuning::eSemitonesValid; + return globalMultichannelTunings[channel][note].semitones; + } + + double freq = global.esp_retuning[note]; + + if (globalTunings[note].freq == freq) + { + if (globalTunings[note].flags & Tuning::eSemitonesValid) + return globalTunings[note].semitones; + + if (globalTunings[note].flags & Tuning::eRatioValid) + { + globalTunings[note].semitones = ratioToSemitones * log(globalTunings[note].ratio); + globalTunings[note].flags |= Tuning::eSemitonesValid; + return globalTunings[note].semitones; + } + } + + globalTunings[note].freq = freq; + globalTunings[note].ratio = freq * global.iet[note]; + globalTunings[note].semitones = ratioToSemitones * log(globalTunings[note].ratio); + globalTunings[note].flags = Tuning::eRatioValid | Tuning::eSemitonesValid; + return globalTunings[note].semitones; + } + + inline bool shouldFilterNote(char midinote, char midichannel) + { + supportsNoteFiltering = true; + supportsMultiChannelNoteFiltering = !(midichannel & ~15); + + if (!freqRequestReceived) + supportsMultiChannelTuning = supportsMultiChannelNoteFiltering; // assume it supports multi channel tuning until a request is received for a frequency and can verify + + if (!global.isOnline()) + return false; + + if (supportsMultiChannelNoteFiltering && + supportsMultiChannelTuning && + global.UseMultiChannelTuning && + global.UseMultiChannelTuning(midichannel)) + { + return global.ShouldFilterNoteMultiChannel ? global.ShouldFilterNoteMultiChannel(midinote & 127, midichannel) : false; + } + + return global.ShouldFilterNote ? global.ShouldFilterNote(midinote & 127, midichannel) : false; + } + + inline char freqToNote(double freq, char midichannel) + { + bool online = global.isOnline(); + bool multiChannel = false; + const double *freqs = online ? global.esp_retuning : localFreqs; + + if (online && + !(midichannel & ~15) && + global.UseMultiChannelTuning && + global.UseMultiChannelTuning(midichannel) && + global.multi_channel_esp_retuning[midichannel & 15]) + { + freqs = global.multi_channel_esp_retuning[midichannel & 15]; + multiChannel = true; + } + + int iLower = 0; + int iUpper = 0; + double dLower = 0.0; + double dUpper = 0.0; + + for (int i = 0; i < 128; i++) + { + if (online) + { + if (multiChannel && + global.ShouldFilterNoteMultiChannel && + global.ShouldFilterNoteMultiChannel(static_cast(i), midichannel)) + { + continue; + } + + if (!multiChannel && + global.ShouldFilterNote && + global.ShouldFilterNote(static_cast(i), midichannel)) + { + continue; + } + } + + double d = freqs[i] - freq; + + if (d == 0.0) + return static_cast(i); + + if (d < 0.0) + { + if (dLower == 0.0 || d > dLower) + { + dLower=d; + iLower=i; + } + } + else if (dUpper == 0.0 || d < dUpper) + { + dUpper = d; + iUpper = i; + } + } + + if (dLower == 0.0) + return static_cast(iUpper); + + if (dUpper == 0.0 || iLower == iUpper) + return static_cast(iLower); + + double fmid = freqs[iLower] * pow(2.0, 0.5 * (log(freqs[iUpper] / freqs[iLower]) / ln2)); + return freq < fmid ? static_cast(iLower) : static_cast(iUpper); + } + + inline char freqToNote(double freq, char *midichannel) + { + if (!midichannel) + return freqToNote(freq, static_cast(-1)); + + if (global.isOnline() && global.UseMultiChannelTuning) + { + int channelsInUse[16]; + int nMultiChannels = 0; + for (int i = 0; i < 16; i++) + if (global.UseMultiChannelTuning(i) && global.multi_channel_esp_retuning[i]) + channelsInUse[nMultiChannels++] = i; + + if (nMultiChannels > 0) + { + const int nFreqs = 128 * nMultiChannels; + int iLower = 0; + int iUpper = 0; + int channel = 0; + int note = 0; + double dLower = 0.0; + double dUpper = 0.0; + + for (int i = 0; i < nFreqs; i++) + { + channel = channelsInUse[i >> 7]; + note = i & 127; + + if (global.ShouldFilterNoteMultiChannel && + global.ShouldFilterNoteMultiChannel(static_cast(note), static_cast(channel))) + { + continue; + } + + double d = global.multi_channel_esp_retuning[channel][note] - freq; + + if (d == 0.0) + { + *midichannel = static_cast(channel); + return static_cast(note); + } + + if (d < 0.0) + { + if (dLower == 0.0 || d > dLower) + { + dLower = d; + iLower = i; + } + } + else if (dUpper == 0.0 || d < dUpper) + { + dUpper = d; + iUpper = i; + } + } + + if (dLower==0.0) + { + *midichannel = static_cast(channelsInUse[iUpper >> 7]); + return static_cast(iUpper & 127); + } + + if (dUpper == 0.0 || iLower == iUpper) + { + *midichannel = static_cast(channelsInUse[iLower >> 7]); + return static_cast(iLower & 127); + } + + double fLower = global.multi_channel_esp_retuning[channelsInUse[iLower >> 7]][iLower & 127]; + double fUpper = global.multi_channel_esp_retuning[channelsInUse[iUpper >> 7]][iUpper & 127]; + double fmid = fLower * pow(2.0, 0.5 * (log(fUpper / fLower) / ln2)); + + if (freq < fmid) + { + *midichannel = static_cast(channelsInUse[iLower >> 7]); + return static_cast(iLower & 127); + } + + *midichannel = static_cast(channelsInUse[iUpper >> 7]); + return static_cast(iUpper & 127); + } + } + + *midichannel = static_cast(0); + return freqToNote(freq, static_cast(0)); + } + + inline void parseMIDIData(const unsigned char *buffer, int len) + { + int sysex_ctr = 0; + int sysex_value = 0; + int note = 0; + int numTunings = 0; + /*int bank = -1, prog = 0, checksum = 0, deviceID = 0; short int channelBitmap = 0; bool realtime = false;*/ // unused for now + + eSysexState state = eIgnoring; + eMTSFormat format = eBulk; + for (int i = 0; i < len; i++) + { + unsigned char b = buffer[i]; + if (b == 0xF7) + { + state = eIgnoring; + continue; + } + + if (b > 0x7F && b != 0xF0) + continue; + + switch (state) + { + case eIgnoring: + if (b == 0xF0) + state = eMatchingSysex; + break; + case eMatchingSysex: + sysex_ctr = 0; + if (b == 0x7E) + state = eSysexValid; + else if (b == 0x7F) + { + /*realtime = true;*/ + state = eSysexValid; + } + else + { + state = eIgnoring; + } + break; + case eSysexValid: + switch (sysex_ctr++) // handle device ID + { + case 0: + /*deviceID = b;*/ + break; + case 1: + if (b == 0x08) + state = eMatchingMTS; + break; + default: // it's not an MTS message + state = eIgnoring; + break; + } + break; + case eMatchingMTS: + sysex_ctr = 0; + switch (b) + { + case 0: + format = eRequest; + state = eMatchingProg; + break; + case 1: + format = eBulk; + state = eMatchingProg; + break; + case 2: + format = eSingle; + state = eMatchingProg; + break; + case 3: + format = eRequest; + state = eMatchingBank; + break; + case 4: + format = eBulk; + state = eMatchingBank; + break; + case 5: + format = eScaleOctOneByte; + state = eMatchingBank; + break; + case 6: + format = eScaleOctTwoByte; + state = eMatchingBank; + break; + case 7: + format = eSingle; + state = eMatchingBank; + break; + case 8: + format = eScaleOctOneByteExt; + state = eMatchingChannel; + break; + case 9: + format = eScaleOctTwoByteExt; + state = eMatchingChannel; + break; + default: // it's not a valid MTS format + state = eIgnoring; + break; + } + break; + case eMatchingBank: + /*bank = b;*/ + state = eMatchingProg; + break; + case eMatchingProg: + /*prog = b;*/ + if (format == eSingle) + { + state = eNumTunings; + } + else + { + state = eTuningName; + tuningName[0] = '\0'; + } + break; + case eTuningName: + tuningName[sysex_ctr] = static_cast(b); + if (++sysex_ctr >= 16) + { + tuningName[16] = '\0'; + sysex_ctr = 0; + state = eTuningData; + } + break; + case eNumTunings: + numTunings = b; + sysex_ctr = 0; + state = eTuningData; + break; + case eMatchingChannel: + switch (sysex_ctr++) + { + case 0: + /*for (int j = 14; j < 16; j++) channelBitmap |= (1 << j);*/ + break; + case 1: + /*for (int j = 7; j < 14; j++) channelBitmap |= (1 << j);*/ + break; + case 2: + /*for (int j = 0; j < 7; j++) channelBitmap |= (1 << j);*/ + sysex_ctr = 0; + state = eTuningData; + break; + } + break; + case eTuningData: + switch (format) + { + case eBulk: + sysex_value = (sysex_value << 7) | b; + sysex_ctr++; + if ((sysex_ctr & 3) == 3) + { + if (!(note == 0x7F && sysex_value == 16383)) + updateTuning(note, (sysex_value >> 14) & 127, (sysex_value & 16383) / 16383.0); + sysex_value = 0; + sysex_ctr++; + if (++note >= 128) + state = eCheckSum; + } + break; + case eSingle: + sysex_value = (sysex_value << 7) | b; + sysex_ctr++; + if (!(sysex_ctr & 3)) + { + if (!(note == 0x7F && sysex_value == 16383)) + updateTuning((sysex_value >> 21) & 127, (sysex_value >> 14) & 127, (sysex_value & 16383) / 16383.0); + sysex_value = 0; + if (++note >= numTunings) + state = eIgnoring; + } + break; + case eScaleOctOneByte: + case eScaleOctOneByteExt: + for (int j = sysex_ctr; j < 128; j += 12) + updateTuning(j, j, (static_cast(b) - 64.0) * 0.01); + if (++sysex_ctr >= 12) + state = format == eScaleOctOneByte ? eCheckSum : eIgnoring; + break; + case eScaleOctTwoByte: + case eScaleOctTwoByteExt: + sysex_value = (sysex_value << 7) | b; + sysex_ctr++; + if (!(sysex_ctr & 1)) + { + double detune = (static_cast(sysex_value & 16383) - 8192.0) / (sysex_value > 8192 ? 8191.0 : 8192.0); + for (int j = note; j < 128; j += 12) + updateTuning(j, j, detune); + if (++note >= 12) + state = format == eScaleOctTwoByte ? eCheckSum : eIgnoring; + } + break; + default: + state = eIgnoring; + break; + } + break; + case eCheckSum: + /*checksum = b;*/ + state = eIgnoring; + break; + } + } + + if (format == eScaleOctOneByte || format == eScaleOctTwoByte || format == eScaleOctOneByteExt || format == eScaleOctTwoByteExt) + { + mapSizeLocal = static_cast(12); + mapStartKeyLocal = static_cast(60); + } + else + { + mapSizeLocal = static_cast(-1); + mapStartKeyLocal = static_cast(-1); + } + } + + inline void updateTuning(int note, int retuneNote, double detune) + { + if (note < 0 || note > 127 || retuneNote < 0 || retuneNote > 127) + return; + receivedMTSSysEx = true; + localFreqs[note] = 440.0 * pow(2.0, ((retuneNote + detune) - 69.0) / 12.0); + if (localFreqs[note] != localTunings[note].freq) + { + localTunings[note].freq = localFreqs[note]; + localTunings[note].flags = 0; + } + } + + inline bool hasReceivedMTSSysEx() {return receivedMTSSysEx;} + + const char *getScaleName() {return (global.isOnline() && global.GetScaleName) ? global.GetScaleName() : tuningName;} + + double getPeriodRatio() {return (global.isOnline() && global.GetPeriodRatio) ? global.GetPeriodRatio() : 2.0;} + double getPeriodSemitones() + { + double periodRatio = getPeriodRatio(); + if (periodRatio != periodRatioLocal) + { + periodSemitones = ratioToSemitones * log(periodRatio); + periodRatioLocal = periodRatio; + } + return periodSemitones; + } + + char getMapSize() {return (global.isOnline() && global.GetMapSize) ? global.GetMapSize() : mapSizeLocal;} + char getMapStartKey() {return (global.isOnline() && global.GetMapStartKey) ? global.GetMapStartKey() : mapStartKeyLocal;} + char getRefKey() {return (global.isOnline() && global.GetRefKey) ? global.GetRefKey() : static_cast(-1);} + + enum eSysexState {eIgnoring = 0, eMatchingSysex, eSysexValid, eMatchingMTS, eMatchingBank, eMatchingProg, eMatchingChannel, eTuningName, eNumTunings, eTuningData, eCheckSum}; + enum eMTSFormat {eRequest = 0, eBulk, eSingle, eScaleOctOneByte, eScaleOctTwoByte, eScaleOctOneByteExt, eScaleOctTwoByteExt}; + + double localFreqs[128]; + Tuning localTunings[128]; + Tuning globalTunings[128]; + Tuning globalMultichannelTunings[16][128]; + + char tuningName[17]; + + double periodRatioLocal; + double periodSemitones; + + char mapSizeLocal; + char mapStartKeyLocal; + + bool supportsNoteFiltering; + bool supportsMultiChannelNoteFiltering; + bool supportsMultiChannelTuning; + bool freqRequestReceived; + bool receivedMTSSysEx; +}; + +static char freqToNoteET(double freq) +{ + static double freqs[128]; + static bool init = false; + if (!init) + { + for (int i = 0; i < 128; i++) + freqs[i] = 440.0 * pow(2.0, (i - 69.0) / 12.0); + init = true; + } + + if (freq <= freqs[0]) + return 0; + if (freq >= freqs[127]) + return 127; + + int mid = 0; + int n = -1; + int n2 = -1; + + for (int first = 0, last=127; + freq != freqs[(mid = first + (last - first) / 2)]; + (freq < freqs[mid]) ? last = mid - 1 : first = mid + 1) + { + if (first > last) + { + if (!mid) + { + n = mid; + break; + } + + if (mid > 127) + mid = 127; + + n = mid - ((freq - freqs[mid - 1]) < (freqs[mid] - freq)); + break; + } + } + + if (n == -1) + { + if (freq == freqs[mid]) + n = mid; + else + return 60; + } + + if (!n) + n2 = 1; + else if (n == 127) + n2 = 126; + else + n2 = n + (fabs(freqs[n - 1] - freq) < fabs(freqs[n + 1] - freq) ? -1 : 1); + + if (n2 < n) + { + int t = n; + n = n2; + n2 = t; + } + + double fmid = freqs[n] * pow(2.0, 0.5 * (log(freqs[n2] / freqs[n]) / ln2)); + return freq < fmid ? static_cast(n) : static_cast(n2); +} + +// exported functions: +MTSClient* MTS_RegisterClient() {return new MTSClient;} +void MTS_DeregisterClient(MTSClient *c) {delete c;} +bool MTS_HasMaster(MTSClient *c) {return c ? c->hasMaster() : false;} +bool MTS_Client_ShouldUpdateLibrary(MTSClient *c) {return c ? c->shouldUpdateLibrary() : false;} +bool MTS_ShouldFilterNote(MTSClient *c, char midinote, char midichannel) {return c ? c->shouldFilterNote(midinote & 127, midichannel) : false;} +double MTS_NoteToFrequency(MTSClient *c, char midinote, char midichannel) {return c ? c->freq(midinote, midichannel) : (1.0 / global.iet[midinote & 127]);} +double MTS_RetuningAsRatio(MTSClient *c, char midinote, char midichannel) {return c ? c->ratio(midinote, midichannel) : 1.0;} +double MTS_RetuningInSemitones(MTSClient *c, char midinote, char midichannel) {return c ? c->semitones(midinote, midichannel) : 0.0;} +char MTS_FrequencyToNote(MTSClient *c, double freq, char midichannel) {return c ? c->freqToNote(freq, midichannel) : freqToNoteET(freq);} +char MTS_FrequencyToNoteAndChannel(MTSClient *c, double freq, char *midichannel) {if (c) return c->freqToNote(freq, midichannel); if (midichannel) *midichannel = 0; return freqToNoteET(freq);} +const char *MTS_GetScaleName(MTSClient *c) {return c ? c->getScaleName() : "";} +double MTS_GetPeriodRatio(MTSClient *c) {return c ? c->getPeriodRatio() : 2.0;} +double MTS_GetPeriodSemitones(MTSClient *c) {return c ? c->getPeriodSemitones() : 12.0;} +char MTS_GetMapSize(MTSClient *c) {return c ? c->getMapSize() : static_cast(-1);} +char MTS_GetMapStartKey(MTSClient *c) {return c ? c->getMapStartKey() : static_cast(-1);} +char MTS_GetRefKey(MTSClient *c) {return c ? c->getRefKey() : static_cast(-1);} +void MTS_ParseMIDIDataU(MTSClient *c, const unsigned char *buffer, int len) {if (c) c->parseMIDIData(buffer, len);} +void MTS_ParseMIDIData(MTSClient *c, const char *buffer, int len) {if (c) c->parseMIDIData(reinterpret_cast(buffer), len);} +bool MTS_HasReceivedMTSSysEx(MTSClient *c) {return c ? c->hasReceivedMTSSysEx() : false;} \ No newline at end of file diff --git a/External/MTS/libMTSClient.h b/External/MTS/libMTSClient.h new file mode 100644 index 0000000..af4ae02 --- /dev/null +++ b/External/MTS/libMTSClient.h @@ -0,0 +1,189 @@ +/* +Copyright (C) 2021 by ODDSound Ltd. info@oddsound.com + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. +*/ + +#ifndef libMTSClient_h +#define libMTSClient_h + +#ifdef __cplusplus +extern "C" { +#endif + + /* + Steps for using the MTS-ESP client API to add microtuning support to a plug-in. + Steps 1 and 2 are required, however it is recommended to include further steps when + integrating: + + + 1. REQUIRED: Register and de-register a plug-in instance as a client with MTS-ESP. + On startup in the plug-in constructor call: + + MTSClient *client = MTS_RegisterClient(); + + Store the returned MTSClient pointer to supply when calling other MTS-ESP client API + functions. On shutdown in the plug-in destructor call: + + MTS_DeregisterClient(client); + + + 2. REQUIRED: Query retuning when a note-on message is received and adjust tuning accordingly. + When given a note call: + + double freq = MTS_NoteToFrequency(client, midinote, midichannel); + OR + double retune_semitones = MTS_RetuningInSemitones(client, midinote, midichannel); + OR + double retune_ratio = MTS_RetuningAsRatio(client, midinote, midichannel); + + MIDI channel arguments should use the range [0,15] however if you don’t know the MIDI + channel, use -1 (see step 6 for more on MIDI channels). + + + 3. RECOMMENDED: Continuously query retuning whilst a note is held, allowing tuning to change + along the flight of a note. Do this if you can and as often as possible, ideally at the same + time as processing any other pitch modulation sources (envelopes, MIDI controllers, LFOs etc.). + + + 4. RECOMMENDED: Provide an option to the user to select whether tuning is queried at note-on + only, as in step 2, or continuously, as in step 3. There are creative and practical + advantages to both, depending on the use case, and offering an option to the user will + provide the most useful MTS-ESP integration. If not offering such an option, continuous + retuning should be preferred over note-on only retuning. + + + 5. RECOMMENDED: Query whether a note should be sounded when a note-on message is received. + The Scala .kbm keyboard mapping format allows for MIDI keys to be unmapped i.e. no frequency + is specified for them, and the MTS-ESP library supports this too. You can query whether a note + is unmapped and should be ignored with: + + bool should_ignore_note = MTS_ShouldFilterNote(client, midinote, midichannel); + + If this returns true, ignore the note-on and don’t play anything. Calling this function is + recommended but optional and a valid value for frequency/retuning will be returned for an + unmapped note. MIDI channel arguments should use the range [0,15] however if you don’t + know the MIDI channel, use -1. + + + 6. RECOMMENDED: Always supply a MIDI channel when querying retuning or note filtering. Doing + so allows your plug-in to use multi-channel tuning tables, useful for microtonal MIDI controllers + with more than 128 keys or working with large scales. Even if multi-channel tables are not + in use, a master may still make use of channel-specific note filtering for functions such as + key switches to change tunings. If your plug-in supports MPE and has a switch for enabling MPE + support, it is recommended to NOT supply a MIDI channel if MPE is enabled. + + + 7. RECOMMENDED: If you are adding MTS-ESP support to a plug-in that already has some kind + of microtuning support, e.g. loading .scl or .tun files, let the local tuning automatically + override MTS-ESP, or provide an option for MTS-ESP retuning to be explicitly disabled. + This affords a user the option to use a different tuning to the global MTS-ESP table + for a specific plug-in instance. + + + 8. OPTIONAL: Add support for MIDI Tuning Standard (or MTS, from the MIDI specification) SysEx + messages to your plug-in. When not connected to an MTS-ESP master plug-in, these can be used + to retune it instead, providing microtuning support even when MTS-ESP is not in use. + When a SysEx message is received, call: + + MTS_ParseMIDIData(client, buffer, len); // if buffer is signed char * + OR + MTS_ParseMIDIDataU(client, buffer, len); // if buffer is unsigned char * + + These will update a local tuning table which is used when querying retuning as in steps 2 + and 3. Check whether a valid MTS SysEx message has been received with: + + bool MTS_SysEx_received = MTS_HasReceivedMTSSysEx(client); + + + 9. OPTIONAL: If you want to display to the user whether the plug-in is "connected" to an + MTS-ESP master plug-in, call: + + bool has_master = MTS_HasMaster(client); + + + 10: OPTIONAL: It is possible to query the name of the current scale. This function is necessarily + supplied for the case where a client is sending MTS SysEx messages, however it can be used + to display the current scale name to the user on your UI too: + + const char *name = MTS_GetScaleName(client); + + + 11: OPTIONAL: After registering, let the user know if they have an older version of the libMTS dynamic library + installed which may not support some features in this version of the API: + + bool should_update = MTS_Client_ShouldUpdateLibrary(client); + + The latest version of libMTS will always be backward compatible with clients built with + an older version of the API. Users can update libMTS using the installers at + https://github.com/ODDSound/MTS-ESP/tree/main/libMTS. + + + 12: EXTRAS: Helper functions are available which return the MIDI note whose pitch is nearest + a given frequency. The MIDI note returned is guaranteed to be mapped. If you intend to + generate a note-on message using the returned note number, you may already know which MIDI + channel it will be sent on, in which case you must specify this in the call, else the client + library can prescribe a channel for you. This is done so that multi-channel mapping + and note filtering can be respected. See below for further details. + */ + + // Opaque datatype for MTSClient. + typedef struct MTSClient MTSClient; + + // Register/deregister as a client. Call from the plug-in constructor and destructor. + extern MTSClient *MTS_RegisterClient(); + extern void MTS_DeregisterClient(MTSClient *client); + + // Check if the client is currently connected to a master plug-in. + extern bool MTS_HasMaster(MTSClient *client); + + // Check if the MTS-ESP dynamic library needs to be updated to use all features in this version of the API. + extern bool MTS_Client_ShouldUpdateLibrary(MTSClient *client); + + // Returns true if note should not be played. MIDI channel argument should be included if possible (0-15), else set to -1. + extern bool MTS_ShouldFilterNote(MTSClient *client, char midinote, char midichannel); + + // Retuning a midi note. Pick the version that makes your life easiest! MIDI channel argument should be included if possible (0-15), else set to -1. + extern double MTS_NoteToFrequency(MTSClient *client, char midinote, char midichannel); + extern double MTS_RetuningInSemitones(MTSClient *client, char midinote, char midichannel); + extern double MTS_RetuningAsRatio(MTSClient *client, char midinote, char midichannel); + + // MTS_FrequencyToNote() is a helper function returning the note number whose pitch is closest to the supplied frequency. Two versions are provided: + // The first is for the simplest case: supply a frequency and get a note number back. + // If you intend to use the returned note number to generate a note-on message on a specific, pre-determined MIDI channel, set the midichannel argument to the destination channel (0-15), else set to -1. + // If a MIDI channel is supplied, the corresponding multi-channel tuning table will be queried if in use, else multi-channel tables are ignored. + extern char MTS_FrequencyToNote(MTSClient *client, double freq, char midichannel); + // Use the second version if you intend to use the returned note number to generate a note-on message and where you have the possibility to send it on any MIDI channel. + // The midichannel argument is a pointer to a char which will receive the MIDI channel on which the note message should be sent (0-15). + // Multi-channel tuning tables are queried if in use. + extern char MTS_FrequencyToNoteAndChannel(MTSClient *client, double freq, char *midichannel); + + // Returns the name of the current scale. + extern const char *MTS_GetScaleName(MTSClient *client); + + // Returns the period of the current scale, or 2.0 (12 semitones) if not supplied by a master. + extern double MTS_GetPeriodRatio(MTSClient *client); + extern double MTS_GetPeriodSemitones(MTSClient *client); + + // Query information about keyboard mapping. + // NOTE: negative values are invalid and these functions will return -1 if the information has not been supplied by a master. + // The return value must therefore be checked it is valid before being used. + extern char MTS_GetMapSize(MTSClient *client); + extern char MTS_GetMapStartKey(MTSClient *client); + extern char MTS_GetRefKey(MTSClient *client); + + // Parse incoming MIDI data to update local tuning. All formats of MTS SysEx message accepted. + extern void MTS_ParseMIDIDataU(MTSClient *client, const unsigned char *buffer, int len); + extern void MTS_ParseMIDIData(MTSClient *client, const char *buffer, int len); + + // Check if the client has received any valid MTS SysEx messages and will use local tuning if not connected to a master plug-in. + extern bool MTS_HasReceivedMTSSysEx(MTSClient *client); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/External/readerwriterqueue/.gitignore b/External/readerwriterqueue/.gitignore new file mode 100644 index 0000000..4e0e82a --- /dev/null +++ b/External/readerwriterqueue/.gitignore @@ -0,0 +1,26 @@ +*.ipch +*.suo +*.user +*.sdf +*.opensdf +*.exe +*.VC.db +.vs/ +tests/stabtest/msvc*/Debug/ +tests/stabtest/msvc*/Release/ +tests/stabtest/msvc*/obj/ +tests/stabtest/msvc*/log.txt +tests/stabtest/log.txt +tests/unittests/msvc*/Debug/ +tests/unittests/msvc*/Release/ +tests/unittests/msvc*/obj/ +tests/CDSChecker/model-checker/ +benchmarks/msvc*/Debug/ +benchmarks/msvc*/Release/ +benchmarks/msvc*/obj/ +test/ +# Linux binaries +benchmarks/benchmarks +tests/stabtest/stabtest +tests/unittests/unittests + diff --git a/External/readerwriterqueue/CMakeLists.txt b/External/readerwriterqueue/CMakeLists.txt new file mode 100644 index 0000000..5ef0caa --- /dev/null +++ b/External/readerwriterqueue/CMakeLists.txt @@ -0,0 +1,70 @@ +# See https://discourse.cmake.org/t/how-to-fix-cmake-minimum-required-deprecation-warning/2487/2 +# for more on setting the minimum required version. + +cmake_minimum_required(VERSION 3.9...3.31.7) +project(readerwriterqueue VERSION 1.0.7) + +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) + +add_library(${PROJECT_NAME} INTERFACE) + +target_include_directories(readerwriterqueue INTERFACE + $ + $ +) + +install(FILES atomicops.h readerwriterqueue.h readerwritercircularbuffer.h LICENSE.md + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}) + +install(TARGETS ${PROJECT_NAME} + EXPORT ${PROJECT_NAME}Targets +) + +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake + VERSION + ${PROJECT_VERSION} + COMPATIBILITY AnyNewerVersion + ARCH_INDEPENDENT +) + +configure_package_config_file(${PROJECT_NAME}Config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake + INSTALL_DESTINATION + ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}/ +) + +install(EXPORT + ${PROJECT_NAME}Targets + FILE + ${PROJECT_NAME}Targets.cmake + NAMESPACE + "${PROJECT_NAME}::" + DESTINATION + ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} + COMPONENT + Devel +) + +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake + DESTINATION + ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} + COMPONENT + Devel +) + +set(CPACK_PACKAGE_NAME ${PROJECT_NAME}) +set(CPACK_PACKAGE_VENDOR "Cameron Desrochers ") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A single-producer, single-consumer lock-free queue for C++.") +set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") +set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}") +set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}") +set(CPACK_PACKAGE_VERSION_PATCH "${PROJECT_VERSION_PATCH}") +set(CPACK_DEBIAN_PACKAGE_MAINTAINER ${CPACK_PACKAGE_VENDOR}) +set(CPACK_GENERATOR "RPM;DEB") + +include(CPack) diff --git a/External/readerwriterqueue/LICENSE.md b/External/readerwriterqueue/LICENSE.md new file mode 100644 index 0000000..7b667d9 --- /dev/null +++ b/External/readerwriterqueue/LICENSE.md @@ -0,0 +1,28 @@ +This license applies to all the code in this repository except that written by third +parties, namely the files in benchmarks/ext, which have their own licenses, and Jeff +Preshing's semaphore implementation (used in the blocking queues) which has a zlib +license (embedded in atomicops.h). + +Simplified BSD License: + +Copyright (c) 2013-2021, Cameron Desrochers +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this list of +conditions and the following disclaimer. +- 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. + +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. diff --git a/External/readerwriterqueue/README.md b/External/readerwriterqueue/README.md new file mode 100644 index 0000000..a241d00 --- /dev/null +++ b/External/readerwriterqueue/README.md @@ -0,0 +1,186 @@ + +# A single-producer, single-consumer lock-free queue for C++ + +This mini-repository has my very own implementation of a lock-free queue (that I designed from scratch) for C++. + +It only supports a two-thread use case (one consuming, and one producing). The threads can't switch roles, though +you could use this queue completely from a single thread if you wish (but that would sort of defeat the purpose!). + +Note: If you need a general-purpose multi-producer, multi-consumer lock free queue, I have [one of those too][mpmc]. + +This repository also includes a [circular-buffer SPSC queue][circular] which supports blocking on enqueue as well as dequeue. + + +## Features + +- [Blazing fast][benchmarks] +- Compatible with C++11 (supports moving objects instead of making copies) +- Fully generic (templated container of any type) -- just like `std::queue`, you never need to allocate memory for elements yourself + (which saves you the hassle of writing a lock-free memory manager to hold the elements you're queueing) +- Allocates memory up front, in contiguous blocks +- Provides a `try_enqueue` method which is guaranteed never to allocate memory (the queue starts with an initial capacity) +- Also provides an `enqueue` method which can dynamically grow the size of the queue as needed +- Also provides `try_emplace`/`emplace` convenience methods +- Has a blocking version with `wait_dequeue` +- Completely "wait-free" (no compare-and-swap loop). Enqueue and dequeue are always O(1) (not counting memory allocation) +- On x86, the memory barriers compile down to no-ops, meaning enqueue and dequeue are just a simple series of loads and stores (and branches) + + +## Use + +Simply drop the readerwriterqueue.h (or readerwritercircularbuffer.h) and atomicops.h files into your source code and include them :-) +A modern compiler is required (MSVC2010+, GCC 4.7+, ICC 13+, or any C++11 compliant compiler should work). + +Note: If you're using GCC, you really do need GCC 4.7 or above -- [4.6 has a bug][gcc46bug] that prevents the atomic fence primitives +from working correctly. + +Example: + +```cpp +using namespace moodycamel; + +ReaderWriterQueue q(100); // Reserve space for at least 100 elements up front + +q.enqueue(17); // Will allocate memory if the queue is full +bool succeeded = q.try_enqueue(18); // Will only succeed if the queue has an empty slot (never allocates) +assert(succeeded); + +int number; +succeeded = q.try_dequeue(number); // Returns false if the queue was empty + +assert(succeeded && number == 17); + +// You can also peek at the front item of the queue (consumer only) +int* front = q.peek(); +assert(*front == 18); +succeeded = q.try_dequeue(number); +assert(succeeded && number == 18); +front = q.peek(); +assert(front == nullptr); // Returns nullptr if the queue was empty +``` + +The blocking version has the exact same API, with the addition of `wait_dequeue` and +`wait_dequeue_timed` methods: + +```cpp +BlockingReaderWriterQueue q; + +std::thread reader([&]() { + int item; +#if 1 + for (int i = 0; i != 100; ++i) { + // Fully-blocking: + q.wait_dequeue(item); + } +#else + for (int i = 0; i != 100; ) { + // Blocking with timeout + if (q.wait_dequeue_timed(item, std::chrono::milliseconds(5))) + ++i; + } +#endif +}); +std::thread writer([&]() { + for (int i = 0; i != 100; ++i) { + q.enqueue(i); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } +}); +writer.join(); +reader.join(); + +assert(q.size_approx() == 0); +``` + +Note that `wait_dequeue` will block indefinitely while the queue is empty; this +means care must be taken to only call `wait_dequeue` if you're sure another element +will come along eventually, or if the queue has a static lifetime. This is because +destroying the queue while a thread is waiting on it will invoke undefined behaviour. + +The blocking circular buffer has a fixed number of slots, but is otherwise quite similar to +use: + +```cpp +BlockingReaderWriterCircularBuffer q(1024); // pass initial capacity + +q.try_enqueue(1); +int number; +q.try_dequeue(number); +assert(number == 1); + +q.wait_enqueue(123); +q.wait_dequeue(number); +assert(number == 123); + +q.wait_dequeue_timed(number, std::chrono::milliseconds(10)); +``` + + +## CMake +### Using targets in your project +Using this project as a part of an existing CMake project is easy. + +In your CMakeLists.txt: +``` +include(FetchContent) + +FetchContent_Declare( + readerwriterqueue + GIT_REPOSITORY https://github.com/cameron314/readerwriterqueue + GIT_TAG master +) + +FetchContent_MakeAvailable(readerwriterqueue) + +add_library(my_target main.cpp) +target_link_libraries(my_target PUBLIC readerwriterqueue) +``` + +In main.cpp: +```cpp +#include + +int main() +{ + moodycamel::ReaderWriterQueue q(100); +} +``` + +### Installing into system directories +As an alternative to including the source files in your project directly, +you can use CMake to install the library in your system's include directory: + +``` +mkdir build +cd build +cmake .. +make install +``` + +Then, you can include it from your source code: +``` +#include +``` + +## Disclaimers + +The queue should only be used on platforms where aligned integer and pointer access is atomic; fortunately, that +includes all modern processors (e.g. x86/x86-64, ARM, and PowerPC). *Not* for use with a DEC Alpha processor (which has very weak memory ordering) :-) + +Note that it's only been tested on x86(-64); if someone has access to other processors I'd love to run some tests on +anything that's not x86-based. + +## More info + +See the [LICENSE.md][license] file for the license (simplified BSD). + +My [blog post][blog] introduces the context that led to this code, and may be of interest if you're curious +about lock-free programming. + + +[blog]: http://moodycamel.com/blog/2013/a-fast-lock-free-queue-for-c++ +[license]: LICENSE.md +[benchmarks]: http://moodycamel.com/blog/2013/a-fast-lock-free-queue-for-c++#benchmarks +[gcc46bug]: http://stackoverflow.com/questions/16429669/stdatomic-thread-fence-has-undefined-reference +[mpmc]: https://github.com/cameron314/concurrentqueue +[circular]: readerwritercircularbuffer.h diff --git a/External/readerwriterqueue/atomicops.h b/External/readerwriterqueue/atomicops.h new file mode 100644 index 0000000..f58bcf8 --- /dev/null +++ b/External/readerwriterqueue/atomicops.h @@ -0,0 +1,772 @@ +// ©2013-2016 Cameron Desrochers. +// Distributed under the simplified BSD license (see the license file that +// should have come with this header). +// Uses Jeff Preshing's semaphore implementation (under the terms of its +// separate zlib license, embedded below). + +#pragma once + +// Provides portable (VC++2010+, Intel ICC 13, GCC 4.7+, and anything C++11 compliant) implementation +// of low-level memory barriers, plus a few semi-portable utility macros (for inlining and alignment). +// Also has a basic atomic type (limited to hardware-supported atomics with no memory ordering guarantees). +// Uses the AE_* prefix for macros (historical reasons), and the "moodycamel" namespace for symbols. + +#include +#include +#include +#include +#include +#include + +// Platform detection +#if defined(__INTEL_COMPILER) +#define AE_ICC +#elif defined(_MSC_VER) +#define AE_VCPP +#elif defined(__GNUC__) +#define AE_GCC +#endif + +#if defined(_M_IA64) || defined(__ia64__) +#define AE_ARCH_IA64 +#elif defined(_WIN64) || defined(__amd64__) || defined(_M_X64) || defined(__x86_64__) +#define AE_ARCH_X64 +#elif defined(_M_IX86) || defined(__i386__) +#define AE_ARCH_X86 +#elif defined(_M_PPC) || defined(__powerpc__) +#define AE_ARCH_PPC +#else +#define AE_ARCH_UNKNOWN +#endif + + +// AE_UNUSED +#define AE_UNUSED(x) ((void)x) + +// AE_NO_TSAN/AE_TSAN_ANNOTATE_* +// For GCC +#if defined(__SANITIZE_THREAD__) +#define AE_TSAN_IS_ENABLED +#endif +// For clang +#if defined(__has_feature) +#if __has_feature(thread_sanitizer) && !defined(AE_TSAN_IS_ENABLED) +#define AE_TSAN_IS_ENABLED +#endif +#endif + +#ifdef AE_TSAN_IS_ENABLED +#if __cplusplus >= 201703L // inline variables require C++17 +namespace moodycamel { inline int ae_tsan_global; } +#define AE_TSAN_ANNOTATE_RELEASE() AnnotateHappensBefore(__FILE__, __LINE__, (void *)(&::moodycamel::ae_tsan_global)) +#define AE_TSAN_ANNOTATE_ACQUIRE() AnnotateHappensAfter(__FILE__, __LINE__, (void *)(&::moodycamel::ae_tsan_global)) +extern "C" void AnnotateHappensBefore(const char*, int, void*); +extern "C" void AnnotateHappensAfter(const char*, int, void*); +#else // when we can't work with tsan, attempt to disable its warnings +#define AE_NO_TSAN __attribute__((no_sanitize("thread"))) +#endif +#endif + +#ifndef AE_NO_TSAN +#define AE_NO_TSAN +#endif + +#ifndef AE_TSAN_ANNOTATE_RELEASE +#define AE_TSAN_ANNOTATE_RELEASE() +#define AE_TSAN_ANNOTATE_ACQUIRE() +#endif + + +// AE_FORCEINLINE +#if defined(AE_VCPP) || defined(AE_ICC) +#define AE_FORCEINLINE __forceinline +#elif defined(AE_GCC) +//#define AE_FORCEINLINE __attribute__((always_inline)) +#define AE_FORCEINLINE inline +#else +#define AE_FORCEINLINE inline +#endif + + +// AE_ALIGN +#if defined(AE_VCPP) || defined(AE_ICC) +#define AE_ALIGN(x) __declspec(align(x)) +#elif defined(AE_GCC) +#define AE_ALIGN(x) __attribute__((aligned(x))) +#else +// Assume GCC compliant syntax... +#define AE_ALIGN(x) __attribute__((aligned(x))) +#endif + + +// Portable atomic fences implemented below: + +namespace moodycamel { + +enum memory_order { + memory_order_relaxed, + memory_order_acquire, + memory_order_release, + memory_order_acq_rel, + memory_order_seq_cst, + + // memory_order_sync: Forces a full sync: + // #LoadLoad, #LoadStore, #StoreStore, and most significantly, #StoreLoad + memory_order_sync = memory_order_seq_cst +}; + +} // end namespace moodycamel + +#if (defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli))) || (defined(AE_ICC) && __INTEL_COMPILER < 1600) +// VS2010 and ICC13 don't support std::atomic_*_fence, implement our own fences + +#include + +#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86) +#define AeFullSync _mm_mfence +#define AeLiteSync _mm_mfence +#elif defined(AE_ARCH_IA64) +#define AeFullSync __mf +#define AeLiteSync __mf +#elif defined(AE_ARCH_PPC) +#include +#define AeFullSync __sync +#define AeLiteSync __lwsync +#endif + + +#ifdef AE_VCPP +#pragma warning(push) +#pragma warning(disable: 4365) // Disable erroneous 'conversion from long to unsigned int, signed/unsigned mismatch' error when using `assert` +#ifdef __cplusplus_cli +#pragma managed(push, off) +#endif +#endif + +namespace moodycamel { + +AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN +{ + switch (order) { + case memory_order_relaxed: break; + case memory_order_acquire: _ReadBarrier(); break; + case memory_order_release: _WriteBarrier(); break; + case memory_order_acq_rel: _ReadWriteBarrier(); break; + case memory_order_seq_cst: _ReadWriteBarrier(); break; + default: assert(false); + } +} + +// x86/x64 have a strong memory model -- all loads and stores have +// acquire and release semantics automatically (so only need compiler +// barriers for those). +#if defined(AE_ARCH_X86) || defined(AE_ARCH_X64) +AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN +{ + switch (order) { + case memory_order_relaxed: break; + case memory_order_acquire: _ReadBarrier(); break; + case memory_order_release: _WriteBarrier(); break; + case memory_order_acq_rel: _ReadWriteBarrier(); break; + case memory_order_seq_cst: + _ReadWriteBarrier(); + AeFullSync(); + _ReadWriteBarrier(); + break; + default: assert(false); + } +} +#else +AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN +{ + // Non-specialized arch, use heavier memory barriers everywhere just in case :-( + switch (order) { + case memory_order_relaxed: + break; + case memory_order_acquire: + _ReadBarrier(); + AeLiteSync(); + _ReadBarrier(); + break; + case memory_order_release: + _WriteBarrier(); + AeLiteSync(); + _WriteBarrier(); + break; + case memory_order_acq_rel: + _ReadWriteBarrier(); + AeLiteSync(); + _ReadWriteBarrier(); + break; + case memory_order_seq_cst: + _ReadWriteBarrier(); + AeFullSync(); + _ReadWriteBarrier(); + break; + default: assert(false); + } +} +#endif +} // end namespace moodycamel +#else +// Use standard library of atomics +#include + +namespace moodycamel { + +AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN +{ + switch (order) { + case memory_order_relaxed: break; + case memory_order_acquire: std::atomic_signal_fence(std::memory_order_acquire); break; + case memory_order_release: std::atomic_signal_fence(std::memory_order_release); break; + case memory_order_acq_rel: std::atomic_signal_fence(std::memory_order_acq_rel); break; + case memory_order_seq_cst: std::atomic_signal_fence(std::memory_order_seq_cst); break; + default: assert(false); + } +} + +AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN +{ + switch (order) { + case memory_order_relaxed: break; + case memory_order_acquire: AE_TSAN_ANNOTATE_ACQUIRE(); std::atomic_thread_fence(std::memory_order_acquire); break; + case memory_order_release: AE_TSAN_ANNOTATE_RELEASE(); std::atomic_thread_fence(std::memory_order_release); break; + case memory_order_acq_rel: AE_TSAN_ANNOTATE_ACQUIRE(); AE_TSAN_ANNOTATE_RELEASE(); std::atomic_thread_fence(std::memory_order_acq_rel); break; + case memory_order_seq_cst: AE_TSAN_ANNOTATE_ACQUIRE(); AE_TSAN_ANNOTATE_RELEASE(); std::atomic_thread_fence(std::memory_order_seq_cst); break; + default: assert(false); + } +} + +} // end namespace moodycamel + +#endif + + +#if !defined(AE_VCPP) || (_MSC_VER >= 1700 && !defined(__cplusplus_cli)) +#define AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC +#endif + +#ifdef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC +#include +#endif +#include + +// WARNING: *NOT* A REPLACEMENT FOR std::atomic. READ CAREFULLY: +// Provides basic support for atomic variables -- no memory ordering guarantees are provided. +// The guarantee of atomicity is only made for types that already have atomic load and store guarantees +// at the hardware level -- on most platforms this generally means aligned pointers and integers (only). +namespace moodycamel { +template +class weak_atomic +{ +public: + AE_NO_TSAN weak_atomic() : value() { } +#ifdef AE_VCPP +#pragma warning(push) +#pragma warning(disable: 4100) // Get rid of (erroneous) 'unreferenced formal parameter' warning +#endif + template AE_NO_TSAN weak_atomic(U&& x) : value(std::forward(x)) { } +#ifdef __cplusplus_cli + // Work around bug with universal reference/nullptr combination that only appears when /clr is on + AE_NO_TSAN weak_atomic(nullptr_t) : value(nullptr) { } +#endif + AE_NO_TSAN weak_atomic(weak_atomic const& other) : value(other.load()) { } + AE_NO_TSAN weak_atomic(weak_atomic&& other) : value(std::move(other.load())) { } +#ifdef AE_VCPP +#pragma warning(pop) +#endif + + AE_FORCEINLINE operator T() const AE_NO_TSAN { return load(); } + + +#ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC + template AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN { value = std::forward(x); return *this; } + AE_FORCEINLINE weak_atomic const& operator=(weak_atomic const& other) AE_NO_TSAN { value = other.value; return *this; } + + AE_FORCEINLINE T load() const AE_NO_TSAN { return value; } + + AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN + { +#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86) + if (sizeof(T) == 4) return _InterlockedExchangeAdd((long volatile*)&value, (long)increment); +#if defined(_M_AMD64) + else if (sizeof(T) == 8) return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment); +#endif +#else +#error Unsupported platform +#endif + assert(false && "T must be either a 32 or 64 bit type"); + return value; + } + + AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN + { +#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86) + if (sizeof(T) == 4) return _InterlockedExchangeAdd((long volatile*)&value, (long)increment); +#if defined(_M_AMD64) + else if (sizeof(T) == 8) return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment); +#endif +#else +#error Unsupported platform +#endif + assert(false && "T must be either a 32 or 64 bit type"); + return value; + } +#else + template + AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN + { + value.store(std::forward(x), std::memory_order_relaxed); + return *this; + } + + AE_FORCEINLINE weak_atomic const& operator=(weak_atomic const& other) AE_NO_TSAN + { + value.store(other.value.load(std::memory_order_relaxed), std::memory_order_relaxed); + return *this; + } + + AE_FORCEINLINE T load() const AE_NO_TSAN { return value.load(std::memory_order_relaxed); } + + AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN + { + return value.fetch_add(increment, std::memory_order_acquire); + } + + AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN + { + return value.fetch_add(increment, std::memory_order_release); + } +#endif + + +private: +#ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC + // No std::atomic support, but still need to circumvent compiler optimizations. + // `volatile` will make memory access slow, but is guaranteed to be reliable. + volatile T value; +#else + std::atomic value; +#endif +}; + +} // end namespace moodycamel + + + +// Portable single-producer, single-consumer semaphore below: + +#if defined(_WIN32) +// Avoid including windows.h in a header; we only need a handful of +// items, so we'll redeclare them here (this is relatively safe since +// the API generally has to remain stable between Windows versions). +// I know this is an ugly hack but it still beats polluting the global +// namespace with thousands of generic names or adding a .cpp for nothing. +extern "C" { + struct _SECURITY_ATTRIBUTES; + __declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes, long lInitialCount, long lMaximumCount, const wchar_t* lpName); + __declspec(dllimport) int __stdcall CloseHandle(void* hObject); + __declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle, unsigned long dwMilliseconds); + __declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount, long* lpPreviousCount); +} +#elif defined(__MACH__) +#include +#elif defined(__unix__) +#include +#elif defined(FREERTOS) +#include +#include +#include +#endif + +namespace moodycamel +{ + // Code in the spsc_sema namespace below is an adaptation of Jeff Preshing's + // portable + lightweight semaphore implementations, originally from + // https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h + // LICENSE: + // Copyright (c) 2015 Jeff Preshing + // + // This software is provided 'as-is', without any express or implied + // warranty. In no event will the authors be held liable for any damages + // arising from the use of this software. + // + // Permission is granted to anyone to use this software for any purpose, + // including commercial applications, and to alter it and redistribute it + // freely, subject to the following restrictions: + // + // 1. The origin of this software must not be misrepresented; you must not + // claim that you wrote the original software. If you use this software + // in a product, an acknowledgement in the product documentation would be + // appreciated but is not required. + // 2. Altered source versions must be plainly marked as such, and must not be + // misrepresented as being the original software. + // 3. This notice may not be removed or altered from any source distribution. + namespace spsc_sema + { +#if defined(_WIN32) + class Semaphore + { + private: + void* m_hSema; + + Semaphore(const Semaphore& other); + Semaphore& operator=(const Semaphore& other); + + public: + AE_NO_TSAN Semaphore(int initialCount = 0) : m_hSema() + { + assert(initialCount >= 0); + const long maxLong = 0x7fffffff; + m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr); + assert(m_hSema); + } + + AE_NO_TSAN ~Semaphore() + { + CloseHandle(m_hSema); + } + + bool wait() AE_NO_TSAN + { + const unsigned long infinite = 0xffffffff; + return WaitForSingleObject(m_hSema, infinite) == 0; + } + + bool try_wait() AE_NO_TSAN + { + return WaitForSingleObject(m_hSema, 0) == 0; + } + + bool timed_wait(std::uint64_t usecs) AE_NO_TSAN + { + return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) == 0; + } + + void signal(int count = 1) AE_NO_TSAN + { + while (!ReleaseSemaphore(m_hSema, count, nullptr)); + } + }; +#elif defined(__MACH__) + //--------------------------------------------------------- + // Semaphore (Apple iOS and OSX) + // Can't use POSIX semaphores due to http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html + //--------------------------------------------------------- + class Semaphore + { + private: + semaphore_t m_sema; + + Semaphore(const Semaphore& other); + Semaphore& operator=(const Semaphore& other); + + public: + AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() + { + assert(initialCount >= 0); + kern_return_t rc = semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount); + assert(rc == KERN_SUCCESS); + AE_UNUSED(rc); + } + + AE_NO_TSAN ~Semaphore() + { + semaphore_destroy(mach_task_self(), m_sema); + } + + bool wait() AE_NO_TSAN + { + return semaphore_wait(m_sema) == KERN_SUCCESS; + } + + bool try_wait() AE_NO_TSAN + { + return timed_wait(0); + } + + bool timed_wait(std::uint64_t timeout_usecs) AE_NO_TSAN + { + mach_timespec_t ts; + ts.tv_sec = static_cast(timeout_usecs / 1000000); + ts.tv_nsec = static_cast((timeout_usecs % 1000000) * 1000); + + // added in OSX 10.10: https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html + kern_return_t rc = semaphore_timedwait(m_sema, ts); + return rc == KERN_SUCCESS; + } + + void signal() AE_NO_TSAN + { + while (semaphore_signal(m_sema) != KERN_SUCCESS); + } + + void signal(int count) AE_NO_TSAN + { + while (count-- > 0) + { + while (semaphore_signal(m_sema) != KERN_SUCCESS); + } + } + }; +#elif defined(__unix__) + //--------------------------------------------------------- + // Semaphore (POSIX, Linux) + //--------------------------------------------------------- + class Semaphore + { + private: + sem_t m_sema; + + Semaphore(const Semaphore& other); + Semaphore& operator=(const Semaphore& other); + + public: + AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() + { + assert(initialCount >= 0); + int rc = sem_init(&m_sema, 0, static_cast(initialCount)); + assert(rc == 0); + AE_UNUSED(rc); + } + + AE_NO_TSAN ~Semaphore() + { + sem_destroy(&m_sema); + } + + bool wait() AE_NO_TSAN + { + // http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error + int rc; + do + { + rc = sem_wait(&m_sema); + } + while (rc == -1 && errno == EINTR); + return rc == 0; + } + + bool try_wait() AE_NO_TSAN + { + int rc; + do { + rc = sem_trywait(&m_sema); + } while (rc == -1 && errno == EINTR); + return rc == 0; + } + + bool timed_wait(std::uint64_t usecs) AE_NO_TSAN + { + struct timespec ts; + const int usecs_in_1_sec = 1000000; + const int nsecs_in_1_sec = 1000000000; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += static_cast(usecs / usecs_in_1_sec); + ts.tv_nsec += static_cast(usecs % usecs_in_1_sec) * 1000; + // sem_timedwait bombs if you have more than 1e9 in tv_nsec + // so we have to clean things up before passing it in + if (ts.tv_nsec >= nsecs_in_1_sec) { + ts.tv_nsec -= nsecs_in_1_sec; + ++ts.tv_sec; + } + + int rc; + do { + rc = sem_timedwait(&m_sema, &ts); + } while (rc == -1 && errno == EINTR); + return rc == 0; + } + + void signal() AE_NO_TSAN + { + while (sem_post(&m_sema) == -1); + } + + void signal(int count) AE_NO_TSAN + { + while (count-- > 0) + { + while (sem_post(&m_sema) == -1); + } + } + }; +#elif defined(FREERTOS) + //--------------------------------------------------------- + // Semaphore (FreeRTOS) + //--------------------------------------------------------- + class Semaphore + { + private: + SemaphoreHandle_t m_sema; + + Semaphore(const Semaphore& other); + Semaphore& operator=(const Semaphore& other); + + public: + AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() + { + assert(initialCount >= 0); + m_sema = xSemaphoreCreateCounting(static_cast(~0ull), static_cast(initialCount)); + assert(m_sema); + } + + AE_NO_TSAN ~Semaphore() + { + vSemaphoreDelete(m_sema); + } + + bool wait() AE_NO_TSAN + { + return xSemaphoreTake(m_sema, portMAX_DELAY) == pdTRUE; + } + + bool try_wait() AE_NO_TSAN + { + // Note: In an ISR context, if this causes a task to unblock, + // the caller won't know about it + if (xPortIsInsideInterrupt()) + return xSemaphoreTakeFromISR(m_sema, NULL) == pdTRUE; + return xSemaphoreTake(m_sema, 0) == pdTRUE; + } + + bool timed_wait(std::uint64_t usecs) AE_NO_TSAN + { + std::uint64_t msecs = usecs / 1000; + TickType_t ticks = static_cast(msecs / portTICK_PERIOD_MS); + if (ticks == 0) + return try_wait(); + return xSemaphoreTake(m_sema, ticks) == pdTRUE; + } + + void signal() AE_NO_TSAN + { + // Note: In an ISR context, if this causes a task to unblock, + // the caller won't know about it + BaseType_t rc; + if (xPortIsInsideInterrupt()) + rc = xSemaphoreGiveFromISR(m_sema, NULL); + else + rc = xSemaphoreGive(m_sema); + assert(rc == pdTRUE); + AE_UNUSED(rc); + } + + void signal(int count) AE_NO_TSAN + { + while (count-- > 0) + signal(); + } + }; +#else +#error Unsupported platform! (No semaphore wrapper available) +#endif + + //--------------------------------------------------------- + // LightweightSemaphore + //--------------------------------------------------------- + class LightweightSemaphore + { + public: + typedef std::make_signed::type ssize_t; + + private: + weak_atomic m_count; + Semaphore m_sema; + + bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1) AE_NO_TSAN + { + ssize_t oldCount; + // Is there a better way to set the initial spin count? + // If we lower it to 1000, testBenaphore becomes 15x slower on my Core i7-5930K Windows PC, + // as threads start hitting the kernel semaphore. + int spin = 1024; + while (--spin >= 0) + { + if (m_count.load() > 0) + { + m_count.fetch_add_acquire(-1); + return true; + } + compiler_fence(memory_order_acquire); // Prevent the compiler from collapsing the loop. + } + oldCount = m_count.fetch_add_acquire(-1); + if (oldCount > 0) + return true; + if (timeout_usecs < 0) + { + if (m_sema.wait()) + return true; + } + if (timeout_usecs > 0 && m_sema.timed_wait(static_cast(timeout_usecs))) + return true; + // At this point, we've timed out waiting for the semaphore, but the + // count is still decremented indicating we may still be waiting on + // it. So we have to re-adjust the count, but only if the semaphore + // wasn't signaled enough times for us too since then. If it was, we + // need to release the semaphore too. + while (true) + { + oldCount = m_count.fetch_add_release(1); + if (oldCount < 0) + return false; // successfully restored things to the way they were + // Oh, the producer thread just signaled the semaphore after all. Try again: + oldCount = m_count.fetch_add_acquire(-1); + if (oldCount > 0 && m_sema.try_wait()) + return true; + } + } + + public: + AE_NO_TSAN LightweightSemaphore(ssize_t initialCount = 0) : m_count(initialCount), m_sema() + { + assert(initialCount >= 0); + } + + bool tryWait() AE_NO_TSAN + { + if (m_count.load() > 0) + { + m_count.fetch_add_acquire(-1); + return true; + } + return false; + } + + bool wait() AE_NO_TSAN + { + return tryWait() || waitWithPartialSpinning(); + } + + bool wait(std::int64_t timeout_usecs) AE_NO_TSAN + { + return tryWait() || waitWithPartialSpinning(timeout_usecs); + } + + void signal(ssize_t count = 1) AE_NO_TSAN + { + assert(count >= 0); + ssize_t oldCount = m_count.fetch_add_release(count); + assert(oldCount >= -1); + if (oldCount < 0) + { + m_sema.signal(1); + } + } + + std::size_t availableApprox() const AE_NO_TSAN + { + ssize_t count = m_count.load(); + return count > 0 ? static_cast(count) : 0; + } + }; + } // end namespace spsc_sema +} // end namespace moodycamel + +#if defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli)) +#pragma warning(pop) +#ifdef __cplusplus_cli +#pragma managed(pop) +#endif +#endif diff --git a/External/readerwriterqueue/benchmarks/bench.cpp b/External/readerwriterqueue/benchmarks/bench.cpp new file mode 100644 index 0000000..6d59d07 --- /dev/null +++ b/External/readerwriterqueue/benchmarks/bench.cpp @@ -0,0 +1,472 @@ +// ©2013-2015 Cameron Desrochers. +// Distributed under the simplified BSD license (see the LICENSE file that +// should have come with this file). + +// Benchmarks for moodycamel::ReaderWriterQueue. + +#if defined(_MSC_VER) && _MSC_VER < 1700 +#define NO_FOLLY_SUPPORT +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1700 +#define NO_CIRCULAR_BUFFER_SUPPORT +#endif + +#if !defined(__amd64__) && !defined(_M_X64) && !defined(__x86_64__) && !defined(_M_IX86) && !defined(__i386__) +#define NO_SPSC_SUPPORT // SPSC implementation is for x86 only +#endif + +#include "ext/1024cores/spscqueue.h" // Dmitry's (on Intel site) +#ifndef NO_FOLLY_SUPPORT +#include "ext/folly/ProducerConsumerQueue.h" // Facebook's folly (GitHub) +#endif +#include "../readerwriterqueue.h" // Mine +#ifndef NO_CIRCULAR_BUFFER_SUPPORT +#include "../readerwritercircularbuffer.h" // Mine +template +class BlockingReaderWriterCircularBufferAdapter : public moodycamel::BlockingReaderWriterCircularBuffer { +public: + BlockingReaderWriterCircularBufferAdapter(std::size_t capacity) : moodycamel::BlockingReaderWriterCircularBuffer(capacity) { } + void enqueue(T const& x) { this->wait_enqueue(x); } +}; +#endif +#include "systemtime.h" +#include "../tests/common/simplethread.h" + +#include +#include +#include // For std::accumulate +#include +#include +#include + +#ifndef UNUSED +#define UNUSED(x) ((void)x); +#endif + +using namespace moodycamel; +#ifndef NO_FOLLY_SUPPORT +using namespace folly; +#endif + + +typedef std::minstd_rand RNG_t; + + +enum BenchmarkType { + bench_raw_add, + bench_raw_remove, + bench_empty_remove, + bench_single_threaded, + bench_mostly_add, + bench_mostly_remove, + bench_heavy_concurrent, + bench_random_concurrent, + + BENCHMARK_COUNT +}; + + +// Returns the number of seconds elapsed (high-precision), and the number of enqueue/dequeue +// operations performed (in the out_Ops parameter) +template +double runBenchmark(BenchmarkType benchmark, unsigned int randomSeed, double& out_Ops); + +const int BENCHMARK_NAME_MAX = 17; // Not including null terminator +const char* benchmarkName(BenchmarkType benchmark); + + +int main(int argc, char** argv) +{ +#ifdef NDEBUG + const int TEST_COUNT = 25; +#else + const int TEST_COUNT = 2; +#endif + assert(TEST_COUNT >= 2); + + const double FASTEST_PERCENT_CONSIDERED = 20; // Consider only the fastest runs in the top 20% + + double rwqResults[BENCHMARK_COUNT][TEST_COUNT]; + double brwcbResults[BENCHMARK_COUNT][TEST_COUNT]; + double spscResults[BENCHMARK_COUNT][TEST_COUNT]; + double follyResults[BENCHMARK_COUNT][TEST_COUNT]; + + // Also calculate a rough heuristic of "ops/s" (across all runs, not just fastest) + double rwqOps[BENCHMARK_COUNT][TEST_COUNT]; + double brwcbOps[BENCHMARK_COUNT][TEST_COUNT]; + double spscOps[BENCHMARK_COUNT][TEST_COUNT]; + double follyOps[BENCHMARK_COUNT][TEST_COUNT]; + + // Make sure the randomness of each benchmark run is identical + unsigned int randSeeds[BENCHMARK_COUNT]; + for (unsigned int i = 0; i != BENCHMARK_COUNT; ++i) { + randSeeds[i] = ((unsigned int)time(NULL)) * i; + } + + // Run benchmarks + for (int benchmark = 0; benchmark < BENCHMARK_COUNT; ++benchmark) { + for (int i = 0; i < TEST_COUNT; ++i) { + rwqResults[benchmark][i] = runBenchmark>((BenchmarkType)benchmark, randSeeds[benchmark], rwqOps[benchmark][i]); + } +#ifndef NO_CIRCULAR_BUFFER_SUPPORT + for (int i = 0; i < TEST_COUNT; ++i) { + brwcbResults[benchmark][i] = runBenchmark>((BenchmarkType)benchmark, randSeeds[benchmark], brwcbOps[benchmark][i]); + } +#else + for (int i = 0; i < TEST_COUNT; ++i) { + brwcbResults[benchmark][i] = 0; + brwcbOps[benchmark][i] = 0; + } +#endif +#ifndef NO_SPSC_SUPPORT + for (int i = 0; i < TEST_COUNT; ++i) { + spscResults[benchmark][i] = runBenchmark>((BenchmarkType)benchmark, randSeeds[benchmark], spscOps[benchmark][i]); + } +#else + for (int i = 0; i < TEST_COUNT; ++i) { + spscResults[benchmark][i] = 0; + spscOps[benchmark][i] = 0; + } +#endif +#ifndef NO_FOLLY_SUPPORT + for (int i = 0; i < TEST_COUNT; ++i) { + follyResults[benchmark][i] = runBenchmark>((BenchmarkType)benchmark, randSeeds[benchmark], follyOps[benchmark][i]); + } +#else + for (int i = 0; i < TEST_COUNT; ++i) { + follyResults[benchmark][i] = 0; + follyOps[benchmark][i] = 0; + } +#endif + } + + // Sort results + for (int benchmark = 0; benchmark < BENCHMARK_COUNT; ++benchmark) { + std::sort(&rwqResults[benchmark][0], &rwqResults[benchmark][0] + TEST_COUNT); + std::sort(&brwcbResults[benchmark][0], &brwcbResults[benchmark][0] + TEST_COUNT); + std::sort(&spscResults[benchmark][0], &spscResults[benchmark][0] + TEST_COUNT); + std::sort(&follyResults[benchmark][0], &follyResults[benchmark][0] + TEST_COUNT); + } + + // Display results + int max = std::max(2, (int)(TEST_COUNT * FASTEST_PERCENT_CONSIDERED / 100)); + assert(max > 0); +#ifdef NO_CIRCULAR_BUFFER_SUPPORT + std::cout << "Note: BRWCB queue not supported on this platform, discount its timings" << std::endl; +#endif +#ifdef NO_SPSC_SUPPORT + std::cout << "Note: SPSC queue not supported on this platform, discount its timings" << std::endl; +#endif +#ifdef NO_FOLLY_SUPPORT + std::cout << "Note: Folly queue not supported by this compiler, discount its timings" << std::endl; +#endif + std::cout << std::setw(BENCHMARK_NAME_MAX) << " " << " |---------------- Min -----------------|----------------- Max -----------------|----------------- Avg -----------------|\n"; + std::cout << std::left << std::setw(BENCHMARK_NAME_MAX) << "Benchmark" << " | RWQ | BRWCB | SPSC | Folly | RWQ | BRWCB | SPSC | Folly | RWQ | BRWCB | SPSC | Folly | xSPSC | xFolly\n"; + std::cout.fill('-'); + std::cout << std::setw(BENCHMARK_NAME_MAX) << "---------" << "-+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+-------+-------\n"; + std::cout.fill(' '); + double rwqOpsPerSec = 0, brwcbOpsPerSec = 0, spscOpsPerSec = 0, follyOpsPerSec = 0; + int opTimedBenchmarks = 0; + for (int benchmark = 0; benchmark < BENCHMARK_COUNT; ++benchmark) { + double rwqMin = rwqResults[benchmark][0], rwqMax = rwqResults[benchmark][max - 1]; + double brwcbMin = brwcbResults[benchmark][0], brwcbMax = brwcbResults[benchmark][max - 1]; + double spscMin = spscResults[benchmark][0], spscMax = spscResults[benchmark][max - 1]; + double follyMin = follyResults[benchmark][0], follyMax = follyResults[benchmark][max - 1]; + double rwqAvg = std::accumulate(&rwqResults[benchmark][0], &rwqResults[benchmark][0] + max, 0.0) / max; + double brwcbAvg = std::accumulate(&brwcbResults[benchmark][0], &brwcbResults[benchmark][0] + max, 0.0) / max; + double spscAvg = std::accumulate(&spscResults[benchmark][0], &spscResults[benchmark][0] + max, 0.0) / max; + double follyAvg = std::accumulate(&follyResults[benchmark][0], &follyResults[benchmark][0] + max, 0.0) / max; + double spscMult = rwqAvg < 0.00001 ? 0 : spscAvg / rwqAvg; + double follyMult = follyAvg < 0.00001 ? 0 : follyAvg / rwqAvg; + + if (rwqResults[benchmark][0] != -1) { + double rwqTotalAvg = std::accumulate(&rwqResults[benchmark][0], &rwqResults[benchmark][0] + TEST_COUNT, 0.0) / TEST_COUNT; + double brwcbTotalAvg = std::accumulate(&brwcbResults[benchmark][0], &brwcbResults[benchmark][0] + TEST_COUNT, 0.0) / TEST_COUNT; + double spscTotalAvg = std::accumulate(&spscResults[benchmark][0], &spscResults[benchmark][0] + TEST_COUNT, 0.0) / TEST_COUNT; + double follyTotalAvg = std::accumulate(&follyResults[benchmark][0], &follyResults[benchmark][0] + TEST_COUNT, 0.0) / TEST_COUNT; + rwqOpsPerSec += rwqTotalAvg == 0 ? 0 : std::accumulate(&rwqOps[benchmark][0], &rwqOps[benchmark][0] + TEST_COUNT, 0.0) / TEST_COUNT / rwqTotalAvg; + brwcbOpsPerSec += brwcbTotalAvg == 0 ? 0 : std::accumulate(&brwcbOps[benchmark][0], &brwcbOps[benchmark][0] + TEST_COUNT, 0.0) / TEST_COUNT / brwcbTotalAvg; + spscOpsPerSec += spscTotalAvg == 0 ? 0 : std::accumulate(&spscOps[benchmark][0], &spscOps[benchmark][0] + TEST_COUNT, 0.0) / TEST_COUNT / spscTotalAvg; + follyOpsPerSec += follyTotalAvg == 0 ? 0 : std::accumulate(&follyOps[benchmark][0], &follyOps[benchmark][0] + TEST_COUNT, 0.0) / TEST_COUNT / follyTotalAvg; + ++opTimedBenchmarks; + } + + std::cout + << std::left << std::setw(BENCHMARK_NAME_MAX) << benchmarkName((BenchmarkType)benchmark) << " | " + << std::fixed << std::setprecision(4) << rwqMin << "s | " + << std::fixed << std::setprecision(4) << brwcbMin << "s | " + << std::fixed << std::setprecision(4) << spscMin << "s | " + << std::fixed << std::setprecision(4) << follyMin << "s | " + << std::fixed << std::setprecision(4) << rwqMax << "s | " + << std::fixed << std::setprecision(4) << brwcbMax << "s | " + << std::fixed << std::setprecision(4) << spscMax << "s | " + << std::fixed << std::setprecision(4) << follyMax << "s | " + << std::fixed << std::setprecision(4) << rwqAvg << "s | " + << std::fixed << std::setprecision(4) << brwcbAvg << "s | " + << std::fixed << std::setprecision(4) << spscAvg << "s | " + << std::fixed << std::setprecision(4) << follyAvg << "s | " + << std::fixed << std::setprecision(2) << spscMult << "x | " + << std::fixed << std::setprecision(2) << follyMult << "x" + << "\n" + ; + } + + rwqOpsPerSec /= opTimedBenchmarks; + brwcbOpsPerSec /= opTimedBenchmarks; + spscOpsPerSec /= opTimedBenchmarks; + follyOpsPerSec /= opTimedBenchmarks; + + std::cout + << "\nAverage ops/s:\n" + << " ReaderWriterQueue: " << std::fixed << std::setprecision(2) << rwqOpsPerSec / 1000000 << " million\n" + << " BlockingReaderWriterCircularBuffer: " << std::fixed << std::setprecision(2) << brwcbOpsPerSec / 1000000 << " million\n" + << " SPSC queue: " << std::fixed << std::setprecision(2) << spscOpsPerSec / 1000000 << " million\n" + << " Folly queue: " << std::fixed << std::setprecision(2) << follyOpsPerSec / 1000000 << " million\n" + ; + std::cout << std::endl; + + return 0; +} + + +template +double runBenchmark(BenchmarkType benchmark, unsigned int randomSeed, double& out_Ops) +{ + typedef unsigned long long counter_t; + + SystemTime start; + double result = 0; + volatile int forceNoOptimizeDummy; + + switch (benchmark) { + case bench_raw_add: { + const counter_t MAX = 100 * 1000; + out_Ops = MAX; + TQueue q(MAX); + int num = 0; + start = getSystemTime(); + for (counter_t i = 0; i != MAX; ++i) { + q.enqueue(num); + ++num; + } + result = getTimeDelta(start); + + int temp = -1; + q.try_dequeue(temp); + forceNoOptimizeDummy = temp; + } break; + case bench_raw_remove: { + const counter_t MAX = 100 * 1000; + out_Ops = MAX; + TQueue q(MAX); + int num = 0; + for (counter_t i = 0; i != MAX; ++i) { + q.enqueue(num); + ++num; + } + + int element = -1; + int total = 0; + num = 0; + start = getSystemTime(); + for (counter_t i = 0; i != MAX; ++i) { + bool success = q.try_dequeue(element); + assert(success && num++ == element); + UNUSED(success); + total += element; + } + result = getTimeDelta(start); + assert(!q.try_dequeue(element)); + forceNoOptimizeDummy = total; + } break; + case bench_empty_remove: { + const counter_t MAX = 2000 * 1000; + out_Ops = MAX; + TQueue q(MAX); + int total = 0; + start = getSystemTime(); + SimpleThread consumer([&]() { + int element; + for (counter_t i = 0; i != MAX; ++i) { + if (q.try_dequeue(element)) { + total += element; + } + } + }); + SimpleThread producer([&]() { + int num = 0; + for (counter_t i = 0; i != MAX / 2; ++i) { + if ((i & 32767) == 0) { // Just to make sure the loops aren't optimized out entirely + q.enqueue(num); + ++num; + } + } + }); + producer.join(); + consumer.join(); + result = getTimeDelta(start); + forceNoOptimizeDummy = total; + } break; + case bench_single_threaded: { + const counter_t MAX = 200 * 1000; + out_Ops = MAX; + RNG_t rng(randomSeed); + std::uniform_int_distribution rand(0, 1); + TQueue q(MAX); + int num = 0; + int element = -1; + start = getSystemTime(); + for (counter_t i = 0; i != MAX; ++i) { + if (rand(rng) == 1) { + q.enqueue(num); + ++num; + } + else { + q.try_dequeue(element); + } + } + result = getTimeDelta(start); + forceNoOptimizeDummy = (int)(q.try_dequeue(element)); + } break; + case bench_mostly_add: { + const counter_t MAX = 1200 * 1000; + out_Ops = MAX; + int readOps = 0; + RNG_t rng(randomSeed); + std::uniform_int_distribution rand(0, 3); + TQueue q(MAX); + int element = -1; + start = getSystemTime(); + SimpleThread consumer([&]() { + for (counter_t i = 0; i != MAX / 10; ++i) { + if (rand(rng) == 0) { + q.try_dequeue(element); + ++readOps; + } + } + }); + SimpleThread producer([&]() { + int num = 0; + for (counter_t i = 0; i != MAX; ++i) { + q.enqueue(num); + ++num; + } + }); + producer.join(); + consumer.join(); + result = getTimeDelta(start); + forceNoOptimizeDummy = (int)(q.try_dequeue(element)); + out_Ops += readOps; + } break; + case bench_mostly_remove: { + const counter_t MAX = 1200 * 1000; + out_Ops = MAX; + int writeOps = 0; + RNG_t rng(randomSeed); + std::uniform_int_distribution rand(0, 3); + TQueue q(MAX); + int element = -1; + start = getSystemTime(); + SimpleThread consumer([&]() { + for (counter_t i = 0; i != MAX; ++i) { + q.try_dequeue(element); + } + }); + SimpleThread producer([&]() { + int num = 0; + for (counter_t i = 0; i != MAX / 10; ++i) { + if (rand(rng) == 0) { + q.enqueue(num); + ++num; + } + } + writeOps = num; + }); + producer.join(); + consumer.join(); + result = getTimeDelta(start); + forceNoOptimizeDummy = (int)(q.try_dequeue(element)); + out_Ops += writeOps; + } break; + case bench_heavy_concurrent: { + const counter_t MAX = 1000 * 1000; + out_Ops = MAX * 2; + TQueue q(MAX); + int element = -1; + start = getSystemTime(); + SimpleThread consumer([&]() { + for (counter_t i = 0; i != MAX; ++i) { + q.try_dequeue(element); + } + }); + SimpleThread producer([&]() { + int num = 0; + for (counter_t i = 0; i != MAX; ++i) { + q.enqueue(num); + ++num; + } + }); + producer.join(); + consumer.join(); + result = getTimeDelta(start); + forceNoOptimizeDummy = (int)(q.try_dequeue(element)); + } break; + case bench_random_concurrent: { + const counter_t MAX = 800 * 1000; + int readOps = 0, writeOps = 0; + TQueue q(MAX); + int element = -1; + start = getSystemTime(); + SimpleThread consumer([&]() { + RNG_t rng(randomSeed); + std::uniform_int_distribution rand(0, 15); + for (counter_t i = 0; i != MAX; ++i) { + if (rand(rng) == 0) { + q.try_dequeue(element); + ++readOps; + } + } + }); + SimpleThread producer([&]() { + RNG_t rng(randomSeed * 3 - 1); + std::uniform_int_distribution rand(0, 15); + int num = 0; + for (counter_t i = 0; i != MAX; ++i) { + if (rand(rng) == 0) { + q.enqueue(num); + ++num; + } + } + writeOps = num; + }); + producer.join(); + consumer.join(); + result = getTimeDelta(start); + forceNoOptimizeDummy = (int)(q.try_dequeue(element)); + out_Ops = readOps + writeOps; + } break; + default: + assert(false); + out_Ops = 0; + return 0; + } + + UNUSED(forceNoOptimizeDummy); + return result / 1000.0; +} + +const char* benchmarkName(BenchmarkType benchmark) +{ + switch (benchmark) { + case bench_raw_add: return "Raw add"; + case bench_raw_remove: return "Raw remove"; + case bench_empty_remove: return "Raw empty remove"; + case bench_single_threaded: return "Single-threaded"; + case bench_mostly_add: return "Mostly add"; + case bench_mostly_remove: return "Mostly remove"; + case bench_heavy_concurrent: return "Heavy concurrent"; + case bench_random_concurrent: return "Random concurrent"; + default: return ""; + } +} diff --git a/External/readerwriterqueue/benchmarks/ext/1024cores/spscqueue.h b/External/readerwriterqueue/benchmarks/ext/1024cores/spscqueue.h new file mode 100644 index 0000000..1b93e83 --- /dev/null +++ b/External/readerwriterqueue/benchmarks/ext/1024cores/spscqueue.h @@ -0,0 +1,139 @@ +#include "../../../atomicops.h" +#include // For std::size_t + +// From http://www.1024cores.net/home/lock-free-algorithms/queues/unbounded-spsc-queue +// (and http://software.intel.com/en-us/articles/single-producer-single-consumer-queue) + +// load with 'consume' (data-dependent) memory ordering +template +T load_consume(T const* addr) +{ + // hardware fence is implicit on x86 + T v = *const_cast(addr); + moodycamel::compiler_fence(moodycamel::memory_order_seq_cst); + return v; +} + +// store with 'release' memory ordering +template +void store_release(T* addr, T v) +{ + // hardware fence is implicit on x86 + moodycamel::compiler_fence(moodycamel::memory_order_seq_cst); + *const_cast(addr) = v; +} + +// cache line size on modern x86 processors (in bytes) +size_t const cache_line_size = 64; +// single-producer/single-consumer queue +template +class spsc_queue +{ +public: + spsc_queue() + { + node* n = new node; + n->next_ = 0; + tail_ = head_ = first_= tail_copy_ = n; + } + + explicit spsc_queue(size_t prealloc) + { + node* n = new node; + n->next_ = 0; + tail_ = head_ = first_ = tail_copy_ = n; + + // [CD] Not (at all) the most efficient way to pre-allocate memory, but it works + T dummy = T(); + for (size_t i = 0; i != prealloc; ++i) { + enqueue(dummy); + } + for (size_t i = 0; i != prealloc; ++i) { + try_dequeue(dummy); + } + } + + ~spsc_queue() + { + node* n = first_; + do + { + node* next = n->next_; + delete n; + n = next; + } + while (n); + } + + void enqueue(T v) + { + node* n = alloc_node(); + n->next_ = 0; + n->value_ = v; + store_release(&head_->next_, n); + head_ = n; + } + + // returns 'false' if queue is empty + bool try_dequeue(T& v) + { + if (load_consume(&tail_->next_)) + { + v = tail_->next_->value_; + store_release(&tail_, tail_->next_); + return true; + } + else + { + return false; + } + } + +private: + // internal node structure + struct node + { + node* next_; + T value_; + }; + + // consumer part + // accessed mainly by consumer, infrequently be producer + node* tail_; // tail of the queue + + // delimiter between consumer part and producer part, + // so that they situated on different cache lines + char cache_line_pad_ [cache_line_size]; + + // producer part + // accessed only by producer + node* head_; // head of the queue + node* first_; // last unused node (tail of node cache) + node* tail_copy_; // helper (points somewhere between first_ and tail_) + + node* alloc_node() + { + // first tries to allocate node from internal node cache, + // if attempt fails, allocates node via ::operator new() + + if (first_ != tail_copy_) + { + node* n = first_; + first_ = first_->next_; + return n; + } + tail_copy_ = load_consume(&tail_); + if (first_ != tail_copy_) + { + node* n = first_; + first_ = first_->next_; + return n; + } + node* n = new node; + return n; + } + + spsc_queue(spsc_queue const&); + spsc_queue& operator = (spsc_queue const&); + +}; diff --git a/External/readerwriterqueue/benchmarks/ext/folly/ProducerConsumerQueue.h b/External/readerwriterqueue/benchmarks/ext/folly/ProducerConsumerQueue.h new file mode 100644 index 0000000..356cdaf --- /dev/null +++ b/External/readerwriterqueue/benchmarks/ext/folly/ProducerConsumerQueue.h @@ -0,0 +1,174 @@ +// Adapted from https://github.com/facebook/folly/blob/master/folly/ProducerConsumerQueue.h +/* + * Copyright 2013 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @author Bo Hu (bhu@fb.com) +// @author Jordan DeLong (delong.j@fb.com) + +#ifndef PRODUCER_CONSUMER_QUEUE_H_ +#define PRODUCER_CONSUMER_QUEUE_H_ + +#include +#include +#include +#include +#include +#include +#include +//#include + +namespace folly { + +/* + * ProducerConsumerQueue is a one producer and one consumer queue + * without locks. + */ +template +struct ProducerConsumerQueue { + typedef T value_type; + + // size must be >= 1. + explicit ProducerConsumerQueue(uint32_t size) + : size_(size + 1) // +1 because one slot is always empty + , records_(static_cast(std::malloc(sizeof(T) * (size + 1)))) + , readIndex_(0) + , writeIndex_(0) + { + assert(size >= 1); + if (!records_) { + throw std::bad_alloc(); + } + } + + ~ProducerConsumerQueue() { + // We need to destruct anything that may still exist in our queue. + // (No real synchronization needed at destructor time: only one + // thread can be doing this.) + if (!std::is_trivially_destructible::value) { + int read = readIndex_; + int end = writeIndex_; + while (read != end) { + records_[read].~T(); + if (++read == size_) { + read = 0; + } + } + } + + std::free(records_); + } + + template + bool enqueue(Args&&... recordArgs) { + auto const currentWrite = writeIndex_.load(std::memory_order_relaxed); + auto nextRecord = currentWrite + 1; + if (nextRecord == size_) { + nextRecord = 0; + } + if (nextRecord != readIndex_.load(std::memory_order_acquire)) { + new (&records_[currentWrite]) T(std::forward(recordArgs)...); + writeIndex_.store(nextRecord, std::memory_order_release); + return true; + } + + // queue is full + return false; + } + + // move (or copy) the value at the front of the queue to given variable + bool try_dequeue(T& record) { + auto const currentRead = readIndex_.load(std::memory_order_relaxed); + if (currentRead == writeIndex_.load(std::memory_order_acquire)) { + // queue is empty + return false; + } + + auto nextRecord = currentRead + 1; + if (nextRecord == size_) { + nextRecord = 0; + } + record = std::move(records_[currentRead]); + records_[currentRead].~T(); + readIndex_.store(nextRecord, std::memory_order_release); + return true; + } + + // pointer to the value at the front of the queue (for use in-place) or + // nullptr if empty. + T* frontPtr() { + auto const currentRead = readIndex_.load(std::memory_order_relaxed); + if (currentRead == writeIndex_.load(std::memory_order_acquire)) { + // queue is empty + return nullptr; + } + return &records_[currentRead]; + } + + // queue must not be empty + void popFront() { + auto const currentRead = readIndex_.load(std::memory_order_relaxed); + assert(currentRead != writeIndex_.load(std::memory_order_acquire)); + + auto nextRecord = currentRead + 1; + if (nextRecord == size_) { + nextRecord = 0; + } + records_[currentRead].~T(); + readIndex_.store(nextRecord, std::memory_order_release); + } + + bool isEmpty() const { + return readIndex_.load(std::memory_order_consume) == + writeIndex_.load(std::memory_order_consume); + } + + bool isFull() const { + auto nextRecord = writeIndex_.load(std::memory_order_consume) + 1; + if (nextRecord == size_) { + nextRecord = 0; + } + if (nextRecord != readIndex_.load(std::memory_order_consume)) { + return false; + } + // queue is full + return true; + } + + // * If called by consumer, then true size may be more (because producer may + // be adding items concurrently). + // * If called by producer, then true size may be less (because consumer may + // be removing items concurrently). + // * It is undefined to call this from any other thread. + size_t sizeGuess() const { + int ret = writeIndex_.load(std::memory_order_consume) - + readIndex_.load(std::memory_order_consume); + if (ret < 0) { + ret += size_; + } + return ret; + } + +private: + const uint32_t size_; + T* const records_; + + std::atomic readIndex_; + std::atomic writeIndex_; +}; + +} + +#endif diff --git a/External/readerwriterqueue/benchmarks/makefile b/External/readerwriterqueue/benchmarks/makefile new file mode 100644 index 0000000..4b33ea3 --- /dev/null +++ b/External/readerwriterqueue/benchmarks/makefile @@ -0,0 +1,22 @@ +# ©2014 Cameron Desrochers + +ifeq ($(OS),Windows_NT) + EXT=.exe + PLATFORM_OPTS=-static +else + EXT= + UNAME_S := $(shell uname -s) + ifeq ($(UNAME_S),Darwin) + PLATFORM_OPTS= + else + PLATFORM_OPTS=-Wl,--no-as-needed -lrt + endif +endif + +default: benchmarks$(EXT) + +benchmarks$(EXT): bench.cpp ../readerwriterqueue.h ../readerwritercircularbuffer.h ../atomicops.h ext/1024cores/spscqueue.h ext/folly/ProducerConsumerQueue.h ../tests/common/simplethread.h ../tests/common/simplethread.cpp systemtime.h systemtime.cpp makefile + g++ -std=c++11 -Wpedantic -Wall -DNDEBUG -O3 -g bench.cpp ../tests/common/simplethread.cpp systemtime.cpp -o benchmarks$(EXT) -pthread $(PLATFORM_OPTS) + +run: benchmarks$(EXT) + ./benchmarks$(EXT) diff --git a/External/readerwriterqueue/benchmarks/msvc10/winbench-intel.vcxproj b/External/readerwriterqueue/benchmarks/msvc10/winbench-intel.vcxproj new file mode 100644 index 0000000..b40edd4 --- /dev/null +++ b/External/readerwriterqueue/benchmarks/msvc10/winbench-intel.vcxproj @@ -0,0 +1,165 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {6B5A3DA6-68D1-46B9-B86C-D02236EABBC9} + Win32Proj + winbenchintel + + + + Application + true + Unicode + Intel C++ Compiler XE 13.0 + + + Application + true + Unicode + Intel C++ Compiler XE 13.0 + + + Application + false + true + Unicode + Intel C++ Compiler XE 13.0 + + + Application + false + true + Unicode + Intel C++ Compiler XE 13.0 + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + true + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + false + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + false + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/External/readerwriterqueue/benchmarks/msvc10/winbench-intel.vcxproj.filters b/External/readerwriterqueue/benchmarks/msvc10/winbench-intel.vcxproj.filters new file mode 100644 index 0000000..a28977c --- /dev/null +++ b/External/readerwriterqueue/benchmarks/msvc10/winbench-intel.vcxproj.filters @@ -0,0 +1,45 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/External/readerwriterqueue/benchmarks/msvc10/winbench.sln b/External/readerwriterqueue/benchmarks/msvc10/winbench.sln new file mode 100644 index 0000000..1eefb56 --- /dev/null +++ b/External/readerwriterqueue/benchmarks/msvc10/winbench.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winbench", "winbench.vcxproj", "{E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winbench-intel", "winbench-intel.vcxproj", "{6B5A3DA6-68D1-46B9-B86C-D02236EABBC9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Debug|Win32.ActiveCfg = Debug|Win32 + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Debug|Win32.Build.0 = Debug|Win32 + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Debug|x64.ActiveCfg = Debug|Win32 + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Debug|x64.Build.0 = Debug|Win32 + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Release|Win32.ActiveCfg = Release|Win32 + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Release|Win32.Build.0 = Release|Win32 + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Release|x64.ActiveCfg = Release|x64 + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Release|x64.Build.0 = Release|x64 + {6B5A3DA6-68D1-46B9-B86C-D02236EABBC9}.Debug|Win32.ActiveCfg = Debug|Win32 + {6B5A3DA6-68D1-46B9-B86C-D02236EABBC9}.Debug|Win32.Build.0 = Debug|Win32 + {6B5A3DA6-68D1-46B9-B86C-D02236EABBC9}.Debug|x64.ActiveCfg = Debug|Win32 + {6B5A3DA6-68D1-46B9-B86C-D02236EABBC9}.Release|Win32.ActiveCfg = Release|Win32 + {6B5A3DA6-68D1-46B9-B86C-D02236EABBC9}.Release|Win32.Build.0 = Release|Win32 + {6B5A3DA6-68D1-46B9-B86C-D02236EABBC9}.Release|x64.ActiveCfg = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/External/readerwriterqueue/benchmarks/msvc10/winbench.vcxproj b/External/readerwriterqueue/benchmarks/msvc10/winbench.vcxproj new file mode 100644 index 0000000..a1da762 --- /dev/null +++ b/External/readerwriterqueue/benchmarks/msvc10/winbench.vcxproj @@ -0,0 +1,161 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2} + Win32Proj + winbench + + + + Application + true + Unicode + + + Application + true + Unicode + + + Application + false + true + Unicode + + + Application + false + true + Unicode + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + true + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + false + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + false + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/External/readerwriterqueue/benchmarks/msvc10/winbench.vcxproj.filters b/External/readerwriterqueue/benchmarks/msvc10/winbench.vcxproj.filters new file mode 100644 index 0000000..d3d09f7 --- /dev/null +++ b/External/readerwriterqueue/benchmarks/msvc10/winbench.vcxproj.filters @@ -0,0 +1,45 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/External/readerwriterqueue/benchmarks/msvc12/winbench-intel.vcxproj b/External/readerwriterqueue/benchmarks/msvc12/winbench-intel.vcxproj new file mode 100644 index 0000000..b40edd4 --- /dev/null +++ b/External/readerwriterqueue/benchmarks/msvc12/winbench-intel.vcxproj @@ -0,0 +1,165 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {6B5A3DA6-68D1-46B9-B86C-D02236EABBC9} + Win32Proj + winbenchintel + + + + Application + true + Unicode + Intel C++ Compiler XE 13.0 + + + Application + true + Unicode + Intel C++ Compiler XE 13.0 + + + Application + false + true + Unicode + Intel C++ Compiler XE 13.0 + + + Application + false + true + Unicode + Intel C++ Compiler XE 13.0 + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + true + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + false + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + false + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/External/readerwriterqueue/benchmarks/msvc12/winbench-intel.vcxproj.filters b/External/readerwriterqueue/benchmarks/msvc12/winbench-intel.vcxproj.filters new file mode 100644 index 0000000..a28977c --- /dev/null +++ b/External/readerwriterqueue/benchmarks/msvc12/winbench-intel.vcxproj.filters @@ -0,0 +1,45 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/External/readerwriterqueue/benchmarks/msvc12/winbench.sln b/External/readerwriterqueue/benchmarks/msvc12/winbench.sln new file mode 100644 index 0000000..b13a224 --- /dev/null +++ b/External/readerwriterqueue/benchmarks/msvc12/winbench.sln @@ -0,0 +1,36 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.30501.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winbench", "winbench.vcxproj", "{E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winbench-intel", "winbench-intel.vcxproj", "{6B5A3DA6-68D1-46B9-B86C-D02236EABBC9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Debug|Win32.ActiveCfg = Debug|Win32 + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Debug|Win32.Build.0 = Debug|Win32 + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Debug|x64.ActiveCfg = Debug|Win32 + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Debug|x64.Build.0 = Debug|Win32 + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Release|Win32.ActiveCfg = Release|Win32 + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Release|Win32.Build.0 = Release|Win32 + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Release|x64.ActiveCfg = Release|x64 + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Release|x64.Build.0 = Release|x64 + {6B5A3DA6-68D1-46B9-B86C-D02236EABBC9}.Debug|Win32.ActiveCfg = Debug|Win32 + {6B5A3DA6-68D1-46B9-B86C-D02236EABBC9}.Debug|Win32.Build.0 = Debug|Win32 + {6B5A3DA6-68D1-46B9-B86C-D02236EABBC9}.Debug|x64.ActiveCfg = Debug|Win32 + {6B5A3DA6-68D1-46B9-B86C-D02236EABBC9}.Release|Win32.ActiveCfg = Release|Win32 + {6B5A3DA6-68D1-46B9-B86C-D02236EABBC9}.Release|Win32.Build.0 = Release|Win32 + {6B5A3DA6-68D1-46B9-B86C-D02236EABBC9}.Release|x64.ActiveCfg = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/External/readerwriterqueue/benchmarks/msvc12/winbench.vcxproj b/External/readerwriterqueue/benchmarks/msvc12/winbench.vcxproj new file mode 100644 index 0000000..8027b6b --- /dev/null +++ b/External/readerwriterqueue/benchmarks/msvc12/winbench.vcxproj @@ -0,0 +1,165 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2} + Win32Proj + winbench + + + + Application + true + Unicode + v120 + + + Application + true + Unicode + v120 + + + Application + false + true + Unicode + v120 + + + Application + false + true + Unicode + v120 + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + true + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + false + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + false + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/External/readerwriterqueue/benchmarks/msvc12/winbench.vcxproj.filters b/External/readerwriterqueue/benchmarks/msvc12/winbench.vcxproj.filters new file mode 100644 index 0000000..b6a4c7b --- /dev/null +++ b/External/readerwriterqueue/benchmarks/msvc12/winbench.vcxproj.filters @@ -0,0 +1,48 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/External/readerwriterqueue/benchmarks/msvc14/winbench-intel.vcxproj b/External/readerwriterqueue/benchmarks/msvc14/winbench-intel.vcxproj new file mode 100644 index 0000000..75ec8eb --- /dev/null +++ b/External/readerwriterqueue/benchmarks/msvc14/winbench-intel.vcxproj @@ -0,0 +1,165 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {6B5A3DA6-68D1-46B9-B86C-D02236EABBC9} + Win32Proj + winbenchintel + + + + Application + true + Unicode + Intel C++ Compiler XE 13.0 + + + Application + true + Unicode + Intel C++ Compiler XE 13.0 + + + Application + false + true + Unicode + Intel C++ Compiler XE 13.0 + + + Application + false + true + Unicode + Intel C++ Compiler XE 13.0 + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + true + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + false + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + false + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/External/readerwriterqueue/benchmarks/msvc14/winbench-intel.vcxproj.filters b/External/readerwriterqueue/benchmarks/msvc14/winbench-intel.vcxproj.filters new file mode 100644 index 0000000..37c1d30 --- /dev/null +++ b/External/readerwriterqueue/benchmarks/msvc14/winbench-intel.vcxproj.filters @@ -0,0 +1,45 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/External/readerwriterqueue/benchmarks/msvc14/winbench.sln b/External/readerwriterqueue/benchmarks/msvc14/winbench.sln new file mode 100644 index 0000000..1f9df57 --- /dev/null +++ b/External/readerwriterqueue/benchmarks/msvc14/winbench.sln @@ -0,0 +1,36 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winbench", "winbench.vcxproj", "{E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winbench-intel", "winbench-intel.vcxproj", "{6B5A3DA6-68D1-46B9-B86C-D02236EABBC9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Debug|Win32.ActiveCfg = Debug|Win32 + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Debug|Win32.Build.0 = Debug|Win32 + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Debug|x64.ActiveCfg = Debug|Win32 + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Debug|x64.Build.0 = Debug|Win32 + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Release|Win32.ActiveCfg = Release|Win32 + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Release|Win32.Build.0 = Release|Win32 + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Release|x64.ActiveCfg = Release|x64 + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2}.Release|x64.Build.0 = Release|x64 + {6B5A3DA6-68D1-46B9-B86C-D02236EABBC9}.Debug|Win32.ActiveCfg = Debug|Win32 + {6B5A3DA6-68D1-46B9-B86C-D02236EABBC9}.Debug|Win32.Build.0 = Debug|Win32 + {6B5A3DA6-68D1-46B9-B86C-D02236EABBC9}.Debug|x64.ActiveCfg = Debug|Win32 + {6B5A3DA6-68D1-46B9-B86C-D02236EABBC9}.Release|Win32.ActiveCfg = Release|Win32 + {6B5A3DA6-68D1-46B9-B86C-D02236EABBC9}.Release|Win32.Build.0 = Release|Win32 + {6B5A3DA6-68D1-46B9-B86C-D02236EABBC9}.Release|x64.ActiveCfg = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/External/readerwriterqueue/benchmarks/msvc14/winbench.vcxproj b/External/readerwriterqueue/benchmarks/msvc14/winbench.vcxproj new file mode 100644 index 0000000..5cac8aa --- /dev/null +++ b/External/readerwriterqueue/benchmarks/msvc14/winbench.vcxproj @@ -0,0 +1,165 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {E58A7EAF-6162-41F1-AD5C-7DD71FB7ABE2} + Win32Proj + winbench + + + + Application + true + Unicode + v140 + + + Application + true + Unicode + v140 + + + Application + false + true + Unicode + v140 + + + Application + false + true + Unicode + v140 + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + true + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + false + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + false + $(SolutionDir)$(Configuration)\$(Platform)\ + obj\$(Configuration)\$(Platform)\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/External/readerwriterqueue/benchmarks/msvc14/winbench.vcxproj.filters b/External/readerwriterqueue/benchmarks/msvc14/winbench.vcxproj.filters new file mode 100644 index 0000000..0128f55 --- /dev/null +++ b/External/readerwriterqueue/benchmarks/msvc14/winbench.vcxproj.filters @@ -0,0 +1,48 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/External/readerwriterqueue/benchmarks/systemtime.cpp b/External/readerwriterqueue/benchmarks/systemtime.cpp new file mode 100644 index 0000000..03c88eb --- /dev/null +++ b/External/readerwriterqueue/benchmarks/systemtime.cpp @@ -0,0 +1,137 @@ +// ©2013-2014 Cameron Desrochers + +#include "systemtime.h" +#include + +#if defined(_MSC_VER) && _MSC_VER < 1700 +#include +#define CompilerMemBar() _ReadWriteBarrier() +#else +#include +#define CompilerMemBar() std::atomic_signal_fence(std::memory_order_seq_cst) +#endif + +#if defined(ST_WINDOWS) + +#include + +namespace moodycamel +{ + +void sleep(int milliseconds) +{ + ::Sleep(milliseconds); +} + +SystemTime getSystemTime() +{ + LARGE_INTEGER t; + CompilerMemBar(); + if (!QueryPerformanceCounter(&t)) { + return static_cast(-1); + } + CompilerMemBar(); + + return static_cast(t.QuadPart); +} + +double getTimeDelta(SystemTime start) +{ + LARGE_INTEGER t; + CompilerMemBar(); + if (start == static_cast(-1) || !QueryPerformanceCounter(&t)) { + return -1; + } + CompilerMemBar(); + + auto now = static_cast(t.QuadPart); + + LARGE_INTEGER f; + if (!QueryPerformanceFrequency(&f)) { + return -1; + } + + return static_cast(static_cast<__int64>(now - start)) / f.QuadPart * 1000; +} + +} // end namespace moodycamel + +#elif defined(ST_APPLE) + +#include +#include +#include +#include + +namespace moodycamel +{ + +void sleep(int milliseconds) +{ + ::usleep(milliseconds * 1000); +} + +SystemTime getSystemTime() +{ + CompilerMemBar(); + std::uint64_t result = mach_absolute_time(); + CompilerMemBar(); + + return result; +} + +double getTimeDelta(SystemTime start) +{ + CompilerMemBar(); + std::uint64_t end = mach_absolute_time(); + CompilerMemBar(); + + mach_timebase_info_data_t tb = { 0 }; + mach_timebase_info(&tb); + double toNano = static_cast(tb.numer) / tb.denom; + + return static_cast(end - start) * toNano * 0.000001; +} + +} // end namespace moodycamel + +#elif defined(ST_NIX) + +#include + +namespace moodycamel +{ + +void sleep(int milliseconds) +{ + ::usleep(milliseconds * 1000); +} + +SystemTime getSystemTime() +{ + timespec t; + CompilerMemBar(); + if (clock_gettime(CLOCK_MONOTONIC_RAW, &t) != 0) { + t.tv_sec = (time_t)-1; + t.tv_nsec = -1; + } + CompilerMemBar(); + + return t; +} + +double getTimeDelta(SystemTime start) +{ + timespec t; + CompilerMemBar(); + if ((start.tv_sec == (time_t)-1 && start.tv_nsec == -1) || clock_gettime(CLOCK_MONOTONIC_RAW, &t) != 0) { + return -1; + } + CompilerMemBar(); + + return static_cast(static_cast(t.tv_sec) - static_cast(start.tv_sec)) * 1000 + double(t.tv_nsec - start.tv_nsec) / 1000000; +} + +} // end namespace moodycamel + +#endif diff --git a/External/readerwriterqueue/benchmarks/systemtime.h b/External/readerwriterqueue/benchmarks/systemtime.h new file mode 100644 index 0000000..3c13112 --- /dev/null +++ b/External/readerwriterqueue/benchmarks/systemtime.h @@ -0,0 +1,33 @@ +// ©2013-2014 Cameron Desrochers + +#pragma once + +#if defined(_WIN32) +#define ST_WINDOWS +#elif defined(__APPLE__) && defined(__MACH__) +#define ST_APPLE +#elif defined(__linux__) || defined(__FreeBSD__) || defined(BSD) +#define ST_NIX +#else +#error "Unknown platform" +#endif + +#if defined(ST_WINDOWS) +namespace moodycamel { typedef unsigned long long SystemTime; } +#elif defined(ST_APPLE) +#include +namespace moodycamel { typedef std::uint64_t SystemTime; } +#elif defined(ST_NIX) +#include +namespace moodycamel { typedef timespec SystemTime; } +#endif + +namespace moodycamel +{ +void sleep(int milliseconds); + +SystemTime getSystemTime(); + +// Returns the delta time, in milliseconds +double getTimeDelta(SystemTime start); +} diff --git a/External/readerwriterqueue/readerwritercircularbuffer.h b/External/readerwriterqueue/readerwritercircularbuffer.h new file mode 100644 index 0000000..072b544 --- /dev/null +++ b/External/readerwriterqueue/readerwritercircularbuffer.h @@ -0,0 +1,321 @@ +// ©2020 Cameron Desrochers. +// Distributed under the simplified BSD license (see the license file that +// should have come with this header). + +// Provides a C++11 implementation of a single-producer, single-consumer wait-free concurrent +// circular buffer (fixed-size queue). + +#pragma once + +#include +#include +#include +#include +#include +#include + +// Note that this implementation is fully modern C++11 (not compatible with old MSVC versions) +// but we still include atomicops.h for its LightweightSemaphore implementation. +#include "atomicops.h" + +#ifndef MOODYCAMEL_CACHE_LINE_SIZE +#define MOODYCAMEL_CACHE_LINE_SIZE 64 +#endif + +namespace moodycamel { + +template +class BlockingReaderWriterCircularBuffer +{ +public: + typedef T value_type; + +public: + explicit BlockingReaderWriterCircularBuffer(std::size_t capacity) + : maxcap(capacity), mask(), rawData(), data(), + slots_(new spsc_sema::LightweightSemaphore(static_cast(capacity))), + items(new spsc_sema::LightweightSemaphore(0)), + nextSlot(0), nextItem(0) + { + // Round capacity up to power of two to compute modulo mask. + // Adapted from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + --capacity; + capacity |= capacity >> 1; + capacity |= capacity >> 2; + capacity |= capacity >> 4; + for (std::size_t i = 1; i < sizeof(std::size_t); i <<= 1) + capacity |= capacity >> (i << 3); + mask = capacity++; + rawData = static_cast(std::malloc(capacity * sizeof(T) + std::alignment_of::value - 1)); + data = align_for(rawData); + } + + BlockingReaderWriterCircularBuffer(BlockingReaderWriterCircularBuffer&& other) + : maxcap(0), mask(0), rawData(nullptr), data(nullptr), + slots_(new spsc_sema::LightweightSemaphore(0)), + items(new spsc_sema::LightweightSemaphore(0)), + nextSlot(), nextItem() + { + swap(other); + } + + BlockingReaderWriterCircularBuffer(BlockingReaderWriterCircularBuffer const&) = delete; + + // Note: The queue should not be accessed concurrently while it's + // being deleted. It's up to the user to synchronize this. + ~BlockingReaderWriterCircularBuffer() + { + for (std::size_t i = 0, n = items->availableApprox(); i != n; ++i) + reinterpret_cast(data)[(nextItem + i) & mask].~T(); + std::free(rawData); + } + + BlockingReaderWriterCircularBuffer& operator=(BlockingReaderWriterCircularBuffer&& other) noexcept + { + swap(other); + return *this; + } + + BlockingReaderWriterCircularBuffer& operator=(BlockingReaderWriterCircularBuffer const&) = delete; + + // Swaps the contents of this buffer with the contents of another. + // Not thread-safe. + void swap(BlockingReaderWriterCircularBuffer& other) noexcept + { + std::swap(maxcap, other.maxcap); + std::swap(mask, other.mask); + std::swap(rawData, other.rawData); + std::swap(data, other.data); + std::swap(slots_, other.slots_); + std::swap(items, other.items); + std::swap(nextSlot, other.nextSlot); + std::swap(nextItem, other.nextItem); + } + + // Enqueues a single item (by copying it). + // Fails if not enough room to enqueue. + // Thread-safe when called by producer thread. + // No exception guarantee (state will be corrupted) if constructor of T throws. + bool try_enqueue(T const& item) + { + if (!slots_->tryWait()) + return false; + inner_enqueue(item); + return true; + } + + // Enqueues a single item (by moving it, if possible). + // Fails if not enough room to enqueue. + // Thread-safe when called by producer thread. + // No exception guarantee (state will be corrupted) if constructor of T throws. + bool try_enqueue(T&& item) + { + if (!slots_->tryWait()) + return false; + inner_enqueue(std::move(item)); + return true; + } + + // Blocks the current thread until there's enough space to enqueue the given item, + // then enqueues it (via copy). + // Thread-safe when called by producer thread. + // No exception guarantee (state will be corrupted) if constructor of T throws. + void wait_enqueue(T const& item) + { + while (!slots_->wait()); + inner_enqueue(item); + } + + // Blocks the current thread until there's enough space to enqueue the given item, + // then enqueues it (via move, if possible). + // Thread-safe when called by producer thread. + // No exception guarantee (state will be corrupted) if constructor of T throws. + void wait_enqueue(T&& item) + { + while (!slots_->wait()); + inner_enqueue(std::move(item)); + } + + // Blocks the current thread until there's enough space to enqueue the given item, + // or the timeout expires. Returns false without enqueueing the item if the timeout + // expires, otherwise enqueues the item (via copy) and returns true. + // Thread-safe when called by producer thread. + // No exception guarantee (state will be corrupted) if constructor of T throws. + bool wait_enqueue_timed(T const& item, std::int64_t timeout_usecs) + { + if (!slots_->wait(timeout_usecs)) + return false; + inner_enqueue(item); + return true; + } + + // Blocks the current thread until there's enough space to enqueue the given item, + // or the timeout expires. Returns false without enqueueing the item if the timeout + // expires, otherwise enqueues the item (via move, if possible) and returns true. + // Thread-safe when called by producer thread. + // No exception guarantee (state will be corrupted) if constructor of T throws. + bool wait_enqueue_timed(T&& item, std::int64_t timeout_usecs) + { + if (!slots_->wait(timeout_usecs)) + return false; + inner_enqueue(std::move(item)); + return true; + } + + // Blocks the current thread until there's enough space to enqueue the given item, + // or the timeout expires. Returns false without enqueueing the item if the timeout + // expires, otherwise enqueues the item (via copy) and returns true. + // Thread-safe when called by producer thread. + // No exception guarantee (state will be corrupted) if constructor of T throws. + template + inline bool wait_enqueue_timed(T const& item, std::chrono::duration const& timeout) + { + return wait_enqueue_timed(item, std::chrono::duration_cast(timeout).count()); + } + + // Blocks the current thread until there's enough space to enqueue the given item, + // or the timeout expires. Returns false without enqueueing the item if the timeout + // expires, otherwise enqueues the item (via move, if possible) and returns true. + // Thread-safe when called by producer thread. + // No exception guarantee (state will be corrupted) if constructor of T throws. + template + inline bool wait_enqueue_timed(T&& item, std::chrono::duration const& timeout) + { + return wait_enqueue_timed(std::move(item), std::chrono::duration_cast(timeout).count()); + } + + // Attempts to dequeue a single item. + // Returns false if the buffer is empty. + // Thread-safe when called by consumer thread. + // No exception guarantee (state will be corrupted) if assignment operator of U throws. + template + bool try_dequeue(U& item) + { + if (!items->tryWait()) + return false; + inner_dequeue(item); + return true; + } + + // Blocks the current thread until there's something to dequeue, then dequeues it. + // Thread-safe when called by consumer thread. + // No exception guarantee (state will be corrupted) if assignment operator of U throws. + template + void wait_dequeue(U& item) + { + while (!items->wait()); + inner_dequeue(item); + } + + // Blocks the current thread until either there's something to dequeue + // or the timeout expires. Returns false without setting `item` if the + // timeout expires, otherwise assigns to `item` and returns true. + // Thread-safe when called by consumer thread. + // No exception guarantee (state will be corrupted) if assignment operator of U throws. + template + bool wait_dequeue_timed(U& item, std::int64_t timeout_usecs) + { + if (!items->wait(timeout_usecs)) + return false; + inner_dequeue(item); + return true; + } + + // Blocks the current thread until either there's something to dequeue + // or the timeout expires. Returns false without setting `item` if the + // timeout expires, otherwise assigns to `item` and returns true. + // Thread-safe when called by consumer thread. + // No exception guarantee (state will be corrupted) if assignment operator of U throws. + template + inline bool wait_dequeue_timed(U& item, std::chrono::duration const& timeout) + { + return wait_dequeue_timed(item, std::chrono::duration_cast(timeout).count()); + } + + // Returns a pointer to the next element in the queue (the one that would + // be removed next by a call to `try_dequeue` or `try_pop`). If the queue + // appears empty at the time the method is called, returns nullptr instead. + // Thread-safe when called by consumer thread. + inline T* peek() + { + if (!items->availableApprox()) + return nullptr; + return inner_peek(); + } + + // Pops the next element from the queue, if there is one. + // Thread-safe when called by consumer thread. + inline bool try_pop() + { + if (!items->tryWait()) + return false; + inner_pop(); + return true; + } + + // Returns a (possibly outdated) snapshot of the total number of elements currently in the buffer. + // Thread-safe. + inline std::size_t size_approx() const + { + return items->availableApprox(); + } + + // Returns the maximum number of elements that this circular buffer can hold at once. + // Thread-safe. + inline std::size_t max_capacity() const + { + return maxcap; + } + +private: + template + void inner_enqueue(U&& item) + { + std::size_t i = nextSlot++; + new (reinterpret_cast(data) + (i & mask)) T(std::forward(item)); + items->signal(); + } + + template + void inner_dequeue(U& item) + { + std::size_t i = nextItem++; + T& element = reinterpret_cast(data)[i & mask]; + item = std::move(element); + element.~T(); + slots_->signal(); + } + + T* inner_peek() + { + return reinterpret_cast(data) + (nextItem & mask); + } + + void inner_pop() + { + std::size_t i = nextItem++; + reinterpret_cast(data)[i & mask].~T(); + slots_->signal(); + } + + template + static inline char* align_for(char* ptr) + { + const std::size_t alignment = std::alignment_of::value; + return ptr + (alignment - (reinterpret_cast(ptr) % alignment)) % alignment; + } + +private: + std::size_t maxcap; // actual (non-power-of-two) capacity + std::size_t mask; // circular buffer capacity mask (for cheap modulo) + char* rawData; // raw circular buffer memory + char* data; // circular buffer memory aligned to element alignment + std::unique_ptr slots_; // number of slots currently free (named with underscore to accommodate Qt's 'slots' macro) + std::unique_ptr items; // number of elements currently enqueued + char cachelineFiller0[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(char*) * 2 - sizeof(std::size_t) * 2 - sizeof(std::unique_ptr) * 2]; + std::size_t nextSlot; // index of next free slot to enqueue into + char cachelineFiller1[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(std::size_t)]; + std::size_t nextItem; // index of next element to dequeue from +}; + +} diff --git a/External/readerwriterqueue/readerwriterqueue.h b/External/readerwriterqueue/readerwriterqueue.h new file mode 100644 index 0000000..78c8e43 --- /dev/null +++ b/External/readerwriterqueue/readerwriterqueue.h @@ -0,0 +1,979 @@ +// ©2013-2020 Cameron Desrochers. +// Distributed under the simplified BSD license (see the license file that +// should have come with this header). + +#pragma once + +#include "atomicops.h" +#include +#include +#include +#include +#include +#include +#include +#include // For malloc/free/abort & size_t +#include +#if __cplusplus > 199711L || _MSC_VER >= 1700 // C++11 or VS2012 +#include +#endif + + +// A lock-free queue for a single-consumer, single-producer architecture. +// The queue is also wait-free in the common path (except if more memory +// needs to be allocated, in which case malloc is called). +// Allocates memory sparingly, and only once if the original maximum size +// estimate is never exceeded. +// Tested on x86/x64 processors, but semantics should be correct for all +// architectures (given the right implementations in atomicops.h), provided +// that aligned integer and pointer accesses are naturally atomic. +// Note that there should only be one consumer thread and producer thread; +// Switching roles of the threads, or using multiple consecutive threads for +// one role, is not safe unless properly synchronized. +// Using the queue exclusively from one thread is fine, though a bit silly. + +#ifndef MOODYCAMEL_CACHE_LINE_SIZE +#define MOODYCAMEL_CACHE_LINE_SIZE 64 +#endif + +#ifndef MOODYCAMEL_EXCEPTIONS_ENABLED +#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || (!defined(_MSC_VER) && !defined(__GNUC__)) +#define MOODYCAMEL_EXCEPTIONS_ENABLED +#endif +#endif + +#ifndef MOODYCAMEL_HAS_EMPLACE +#if !defined(_MSC_VER) || _MSC_VER >= 1800 // variadic templates: either a non-MS compiler or VS >= 2013 +#define MOODYCAMEL_HAS_EMPLACE 1 +#endif +#endif + +#ifndef MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE +#if defined (__APPLE__) && defined (__MACH__) && __cplusplus >= 201703L +// This is required to find out what deployment target we are using +#include +#if !defined(MAC_OS_X_VERSION_MIN_REQUIRED) || !defined(MAC_OS_X_VERSION_10_14) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_14 +// C++17 new(size_t, align_val_t) is not backwards-compatible with older versions of macOS, so we can't support over-alignment in this case +#define MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE +#endif +#endif +#endif + +#ifndef MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE +#define MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE AE_ALIGN(MOODYCAMEL_CACHE_LINE_SIZE) +#endif + +#ifdef AE_VCPP +#pragma warning(push) +#pragma warning(disable: 4324) // structure was padded due to __declspec(align()) +#pragma warning(disable: 4820) // padding was added +#pragma warning(disable: 4127) // conditional expression is constant +#endif + +namespace moodycamel { + +template +class MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE ReaderWriterQueue +{ + // Design: Based on a queue-of-queues. The low-level queues are just + // circular buffers with front and tail indices indicating where the + // next element to dequeue is and where the next element can be enqueued, + // respectively. Each low-level queue is called a "block". Each block + // wastes exactly one element's worth of space to keep the design simple + // (if front == tail then the queue is empty, and can't be full). + // The high-level queue is a circular linked list of blocks; again there + // is a front and tail, but this time they are pointers to the blocks. + // The front block is where the next element to be dequeued is, provided + // the block is not empty. The back block is where elements are to be + // enqueued, provided the block is not full. + // The producer thread owns all the tail indices/pointers. The consumer + // thread owns all the front indices/pointers. Both threads read each + // other's variables, but only the owning thread updates them. E.g. After + // the consumer reads the producer's tail, the tail may change before the + // consumer is done dequeuing an object, but the consumer knows the tail + // will never go backwards, only forwards. + // If there is no room to enqueue an object, an additional block (of + // equal size to the last block) is added. Blocks are never removed. + +public: + typedef T value_type; + + // Constructs a queue that can hold at least `size` elements without further + // allocations. If more than MAX_BLOCK_SIZE elements are requested, + // then several blocks of MAX_BLOCK_SIZE each are reserved (including + // at least one extra buffer block). + AE_NO_TSAN explicit ReaderWriterQueue(size_t size = 15) +#ifndef NDEBUG + : enqueuing(false) + ,dequeuing(false) +#endif + { + assert(MAX_BLOCK_SIZE == ceilToPow2(MAX_BLOCK_SIZE) && "MAX_BLOCK_SIZE must be a power of 2"); + assert(MAX_BLOCK_SIZE >= 2 && "MAX_BLOCK_SIZE must be at least 2"); + + Block* firstBlock = nullptr; + + largestBlockSize = ceilToPow2(size + 1); // We need a spare slot to fit size elements in the block + if (largestBlockSize > MAX_BLOCK_SIZE * 2) { + // We need a spare block in case the producer is writing to a different block the consumer is reading from, and + // wants to enqueue the maximum number of elements. We also need a spare element in each block to avoid the ambiguity + // between front == tail meaning "empty" and "full". + // So the effective number of slots that are guaranteed to be usable at any time is the block size - 1 times the + // number of blocks - 1. Solving for size and applying a ceiling to the division gives us (after simplifying): + size_t initialBlockCount = (size + MAX_BLOCK_SIZE * 2 - 3) / (MAX_BLOCK_SIZE - 1); + largestBlockSize = MAX_BLOCK_SIZE; + Block* lastBlock = nullptr; + for (size_t i = 0; i != initialBlockCount; ++i) { + auto block = make_block(largestBlockSize); + if (block == nullptr) { +#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED + throw std::bad_alloc(); +#else + abort(); +#endif + } + if (firstBlock == nullptr) { + firstBlock = block; + } + else { + lastBlock->next = block; + } + lastBlock = block; + block->next = firstBlock; + } + } + else { + firstBlock = make_block(largestBlockSize); + if (firstBlock == nullptr) { +#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED + throw std::bad_alloc(); +#else + abort(); +#endif + } + firstBlock->next = firstBlock; + } + frontBlock = firstBlock; + tailBlock = firstBlock; + + // Make sure the reader/writer threads will have the initialized memory setup above: + fence(memory_order_sync); + } + + // Note: The queue should not be accessed concurrently while it's + // being moved. It's up to the user to synchronize this. + AE_NO_TSAN ReaderWriterQueue(ReaderWriterQueue&& other) + : frontBlock(other.frontBlock.load()), + tailBlock(other.tailBlock.load()), + largestBlockSize(other.largestBlockSize) +#ifndef NDEBUG + ,enqueuing(false) + ,dequeuing(false) +#endif + { + other.largestBlockSize = 32; + Block* b = other.make_block(other.largestBlockSize); + if (b == nullptr) { +#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED + throw std::bad_alloc(); +#else + abort(); +#endif + } + b->next = b; + other.frontBlock = b; + other.tailBlock = b; + } + + // Note: The queue should not be accessed concurrently while it's + // being moved. It's up to the user to synchronize this. + ReaderWriterQueue& operator=(ReaderWriterQueue&& other) AE_NO_TSAN + { + Block* b = frontBlock.load(); + frontBlock = other.frontBlock.load(); + other.frontBlock = b; + b = tailBlock.load(); + tailBlock = other.tailBlock.load(); + other.tailBlock = b; + std::swap(largestBlockSize, other.largestBlockSize); + return *this; + } + + // Note: The queue should not be accessed concurrently while it's + // being deleted. It's up to the user to synchronize this. + AE_NO_TSAN ~ReaderWriterQueue() + { + // Make sure we get the latest version of all variables from other CPUs: + fence(memory_order_sync); + + // Destroy any remaining objects in queue and free memory + Block* frontBlock_ = frontBlock; + Block* block = frontBlock_; + do { + Block* nextBlock = block->next; + size_t blockFront = block->front; + size_t blockTail = block->tail; + + for (size_t i = blockFront; i != blockTail; i = (i + 1) & block->sizeMask) { + auto element = reinterpret_cast(block->data + i * sizeof(T)); + element->~T(); + (void)element; + } + + auto rawBlock = block->rawThis; + block->~Block(); + std::free(rawBlock); + block = nextBlock; + } while (block != frontBlock_); + } + + + // Enqueues a copy of element if there is room in the queue. + // Returns true if the element was enqueued, false otherwise. + // Does not allocate memory. + AE_FORCEINLINE bool try_enqueue(T const& element) AE_NO_TSAN + { + return inner_enqueue(element); + } + + // Enqueues a moved copy of element if there is room in the queue. + // Returns true if the element was enqueued, false otherwise. + // Does not allocate memory. + AE_FORCEINLINE bool try_enqueue(T&& element) AE_NO_TSAN + { + return inner_enqueue(std::forward(element)); + } + +#if MOODYCAMEL_HAS_EMPLACE + // Like try_enqueue() but with emplace semantics (i.e. construct-in-place). + template + AE_FORCEINLINE bool try_emplace(Args&&... args) AE_NO_TSAN + { + return inner_enqueue(std::forward(args)...); + } +#endif + + // Enqueues a copy of element on the queue. + // Allocates an additional block of memory if needed. + // Only fails (returns false) if memory allocation fails. + AE_FORCEINLINE bool enqueue(T const& element) AE_NO_TSAN + { + return inner_enqueue(element); + } + + // Enqueues a moved copy of element on the queue. + // Allocates an additional block of memory if needed. + // Only fails (returns false) if memory allocation fails. + AE_FORCEINLINE bool enqueue(T&& element) AE_NO_TSAN + { + return inner_enqueue(std::forward(element)); + } + +#if MOODYCAMEL_HAS_EMPLACE + // Like enqueue() but with emplace semantics (i.e. construct-in-place). + template + AE_FORCEINLINE bool emplace(Args&&... args) AE_NO_TSAN + { + return inner_enqueue(std::forward(args)...); + } +#endif + + // Attempts to dequeue an element; if the queue is empty, + // returns false instead. If the queue has at least one element, + // moves front to result using operator=, then returns true. + template + bool try_dequeue(U& result) AE_NO_TSAN + { +#ifndef NDEBUG + ReentrantGuard guard(this->dequeuing); +#endif + + // High-level pseudocode: + // Remember where the tail block is + // If the front block has an element in it, dequeue it + // Else + // If front block was the tail block when we entered the function, return false + // Else advance to next block and dequeue the item there + + // Note that we have to use the value of the tail block from before we check if the front + // block is full or not, in case the front block is empty and then, before we check if the + // tail block is at the front block or not, the producer fills up the front block *and + // moves on*, which would make us skip a filled block. Seems unlikely, but was consistently + // reproducible in practice. + // In order to avoid overhead in the common case, though, we do a double-checked pattern + // where we have the fast path if the front block is not empty, then read the tail block, + // then re-read the front block and check if it's not empty again, then check if the tail + // block has advanced. + + Block* frontBlock_ = frontBlock.load(); + size_t blockTail = frontBlock_->localTail; + size_t blockFront = frontBlock_->front.load(); + + if (blockFront != blockTail || blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) { + fence(memory_order_acquire); + + non_empty_front_block: + // Front block not empty, dequeue from here + auto element = reinterpret_cast(frontBlock_->data + blockFront * sizeof(T)); + result = std::move(*element); + element->~T(); + + blockFront = (blockFront + 1) & frontBlock_->sizeMask; + + fence(memory_order_release); + frontBlock_->front = blockFront; + } + else if (frontBlock_ != tailBlock.load()) { + fence(memory_order_acquire); + + frontBlock_ = frontBlock.load(); + blockTail = frontBlock_->localTail = frontBlock_->tail.load(); + blockFront = frontBlock_->front.load(); + fence(memory_order_acquire); + + if (blockFront != blockTail) { + // Oh look, the front block isn't empty after all + goto non_empty_front_block; + } + + // Front block is empty but there's another block ahead, advance to it + Block* nextBlock = frontBlock_->next; + // Don't need an acquire fence here since next can only ever be set on the tailBlock, + // and we're not the tailBlock, and we did an acquire earlier after reading tailBlock which + // ensures next is up-to-date on this CPU in case we recently were at tailBlock. + + size_t nextBlockFront = nextBlock->front.load(); + size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load(); + fence(memory_order_acquire); + + // Since the tailBlock is only ever advanced after being written to, + // we know there's for sure an element to dequeue on it + assert(nextBlockFront != nextBlockTail); + AE_UNUSED(nextBlockTail); + + // We're done with this block, let the producer use it if it needs + fence(memory_order_release); // Expose possibly pending changes to frontBlock->front from last dequeue + frontBlock = frontBlock_ = nextBlock; + + compiler_fence(memory_order_release); // Not strictly needed + + auto element = reinterpret_cast(frontBlock_->data + nextBlockFront * sizeof(T)); + + result = std::move(*element); + element->~T(); + + nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask; + + fence(memory_order_release); + frontBlock_->front = nextBlockFront; + } + else { + // No elements in current block and no other block to advance to + return false; + } + + return true; + } + + + // Returns a pointer to the front element in the queue (the one that + // would be removed next by a call to `try_dequeue` or `pop`). If the + // queue appears empty at the time the method is called, nullptr is + // returned instead. + // Must be called only from the consumer thread. + T* peek() const AE_NO_TSAN + { +#ifndef NDEBUG + ReentrantGuard guard(this->dequeuing); +#endif + // See try_dequeue() for reasoning + + Block* frontBlock_ = frontBlock.load(); + size_t blockTail = frontBlock_->localTail; + size_t blockFront = frontBlock_->front.load(); + + if (blockFront != blockTail || blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) { + fence(memory_order_acquire); + non_empty_front_block: + return reinterpret_cast(frontBlock_->data + blockFront * sizeof(T)); + } + else if (frontBlock_ != tailBlock.load()) { + fence(memory_order_acquire); + frontBlock_ = frontBlock.load(); + blockTail = frontBlock_->localTail = frontBlock_->tail.load(); + blockFront = frontBlock_->front.load(); + fence(memory_order_acquire); + + if (blockFront != blockTail) { + goto non_empty_front_block; + } + + Block* nextBlock = frontBlock_->next; + + size_t nextBlockFront = nextBlock->front.load(); + fence(memory_order_acquire); + + assert(nextBlockFront != nextBlock->tail.load()); + return reinterpret_cast(nextBlock->data + nextBlockFront * sizeof(T)); + } + + return nullptr; + } + + // Removes the front element from the queue, if any, without returning it. + // Returns true on success, or false if the queue appeared empty at the time + // `pop` was called. + bool pop() AE_NO_TSAN + { +#ifndef NDEBUG + ReentrantGuard guard(this->dequeuing); +#endif + // See try_dequeue() for reasoning + + Block* frontBlock_ = frontBlock.load(); + size_t blockTail = frontBlock_->localTail; + size_t blockFront = frontBlock_->front.load(); + + if (blockFront != blockTail || blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) { + fence(memory_order_acquire); + + non_empty_front_block: + auto element = reinterpret_cast(frontBlock_->data + blockFront * sizeof(T)); + element->~T(); + + blockFront = (blockFront + 1) & frontBlock_->sizeMask; + + fence(memory_order_release); + frontBlock_->front = blockFront; + } + else if (frontBlock_ != tailBlock.load()) { + fence(memory_order_acquire); + frontBlock_ = frontBlock.load(); + blockTail = frontBlock_->localTail = frontBlock_->tail.load(); + blockFront = frontBlock_->front.load(); + fence(memory_order_acquire); + + if (blockFront != blockTail) { + goto non_empty_front_block; + } + + // Front block is empty but there's another block ahead, advance to it + Block* nextBlock = frontBlock_->next; + + size_t nextBlockFront = nextBlock->front.load(); + size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load(); + fence(memory_order_acquire); + + assert(nextBlockFront != nextBlockTail); + AE_UNUSED(nextBlockTail); + + fence(memory_order_release); + frontBlock = frontBlock_ = nextBlock; + + compiler_fence(memory_order_release); + + auto element = reinterpret_cast(frontBlock_->data + nextBlockFront * sizeof(T)); + element->~T(); + + nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask; + + fence(memory_order_release); + frontBlock_->front = nextBlockFront; + } + else { + // No elements in current block and no other block to advance to + return false; + } + + return true; + } + + // Returns the approximate number of items currently in the queue. + // Safe to call from both the producer and consumer threads. + inline size_t size_approx() const AE_NO_TSAN + { + size_t result = 0; + Block* frontBlock_ = frontBlock.load(); + Block* block = frontBlock_; + do { + fence(memory_order_acquire); + size_t blockFront = block->front.load(); + size_t blockTail = block->tail.load(); + result += (blockTail - blockFront) & block->sizeMask; + block = block->next.load(); + } while (block != frontBlock_); + return result; + } + + // Returns the total number of items that could be enqueued without incurring + // an allocation when this queue is empty. + // Safe to call from both the producer and consumer threads. + // + // NOTE: The actual capacity during usage may be different depending on the consumer. + // If the consumer is removing elements concurrently, the producer cannot add to + // the block the consumer is removing from until it's completely empty, except in + // the case where the producer was writing to the same block the consumer was + // reading from the whole time. + inline size_t max_capacity() const { + size_t result = 0; + Block* frontBlock_ = frontBlock.load(); + Block* block = frontBlock_; + do { + fence(memory_order_acquire); + result += block->sizeMask; + block = block->next.load(); + } while (block != frontBlock_); + return result; + } + + +private: + enum AllocationMode { CanAlloc, CannotAlloc }; + +#if MOODYCAMEL_HAS_EMPLACE + template + bool inner_enqueue(Args&&... args) AE_NO_TSAN +#else + template + bool inner_enqueue(U&& element) AE_NO_TSAN +#endif + { +#ifndef NDEBUG + ReentrantGuard guard(this->enqueuing); +#endif + + // High-level pseudocode (assuming we're allowed to alloc a new block): + // If room in tail block, add to tail + // Else check next block + // If next block is not the head block, enqueue on next block + // Else create a new block and enqueue there + // Advance tail to the block we just enqueued to + + Block* tailBlock_ = tailBlock.load(); + size_t blockFront = tailBlock_->localFront; + size_t blockTail = tailBlock_->tail.load(); + + size_t nextBlockTail = (blockTail + 1) & tailBlock_->sizeMask; + if (nextBlockTail != blockFront || nextBlockTail != (tailBlock_->localFront = tailBlock_->front.load())) { + fence(memory_order_acquire); + // This block has room for at least one more element + char* location = tailBlock_->data + blockTail * sizeof(T); +#if MOODYCAMEL_HAS_EMPLACE + new (location) T(std::forward(args)...); +#else + new (location) T(std::forward(element)); +#endif + + fence(memory_order_release); + tailBlock_->tail = nextBlockTail; + } + else { + fence(memory_order_acquire); + if (tailBlock_->next.load() != frontBlock) { + // Note that the reason we can't advance to the frontBlock and start adding new entries there + // is because if we did, then dequeue would stay in that block, eventually reading the new values, + // instead of advancing to the next full block (whose values were enqueued first and so should be + // consumed first). + + fence(memory_order_acquire); // Ensure we get latest writes if we got the latest frontBlock + + // tailBlock is full, but there's a free block ahead, use it + Block* tailBlockNext = tailBlock_->next.load(); + size_t nextBlockFront = tailBlockNext->localFront = tailBlockNext->front.load(); + nextBlockTail = tailBlockNext->tail.load(); + fence(memory_order_acquire); + + // This block must be empty since it's not the head block and we + // go through the blocks in a circle + assert(nextBlockFront == nextBlockTail); + tailBlockNext->localFront = nextBlockFront; + + char* location = tailBlockNext->data + nextBlockTail * sizeof(T); +#if MOODYCAMEL_HAS_EMPLACE + new (location) T(std::forward(args)...); +#else + new (location) T(std::forward(element)); +#endif + + tailBlockNext->tail = (nextBlockTail + 1) & tailBlockNext->sizeMask; + + fence(memory_order_release); + tailBlock = tailBlockNext; + } + else if (canAlloc == CanAlloc) { + // tailBlock is full and there's no free block ahead; create a new block + auto newBlockSize = largestBlockSize >= MAX_BLOCK_SIZE ? largestBlockSize : largestBlockSize * 2; + auto newBlock = make_block(newBlockSize); + if (newBlock == nullptr) { + // Could not allocate a block! + return false; + } + largestBlockSize = newBlockSize; + +#if MOODYCAMEL_HAS_EMPLACE + new (newBlock->data) T(std::forward(args)...); +#else + new (newBlock->data) T(std::forward(element)); +#endif + assert(newBlock->front == 0); + newBlock->tail = newBlock->localTail = 1; + + newBlock->next = tailBlock_->next.load(); + tailBlock_->next = newBlock; + + // Might be possible for the dequeue thread to see the new tailBlock->next + // *without* seeing the new tailBlock value, but this is OK since it can't + // advance to the next block until tailBlock is set anyway (because the only + // case where it could try to read the next is if it's already at the tailBlock, + // and it won't advance past tailBlock in any circumstance). + + fence(memory_order_release); + tailBlock = newBlock; + } + else if (canAlloc == CannotAlloc) { + // Would have had to allocate a new block to enqueue, but not allowed + return false; + } + else { + assert(false && "Should be unreachable code"); + return false; + } + } + + return true; + } + + + // Disable copying + ReaderWriterQueue(ReaderWriterQueue const&) { } + + // Disable assignment + ReaderWriterQueue& operator=(ReaderWriterQueue const&) { } + + + AE_FORCEINLINE static size_t ceilToPow2(size_t x) + { + // From http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + --x; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + for (size_t i = 1; i < sizeof(size_t); i <<= 1) { + x |= x >> (i << 3); + } + ++x; + return x; + } + + template + static AE_FORCEINLINE char* align_for(char* ptr) AE_NO_TSAN + { + const std::size_t alignment = std::alignment_of::value; + return ptr + (alignment - (reinterpret_cast(ptr) % alignment)) % alignment; + } +private: +#ifndef NDEBUG + struct ReentrantGuard + { + AE_NO_TSAN ReentrantGuard(weak_atomic& _inSection) + : inSection(_inSection) + { + assert(!inSection && "Concurrent (or re-entrant) enqueue or dequeue operation detected (only one thread at a time may hold the producer or consumer role)"); + inSection = true; + } + + AE_NO_TSAN ~ReentrantGuard() { inSection = false; } + + private: + ReentrantGuard& operator=(ReentrantGuard const&); + + private: + weak_atomic& inSection; + }; +#endif + + struct Block + { + // Avoid false-sharing by putting highly contended variables on their own cache lines + weak_atomic front; // (Atomic) Elements are read from here + size_t localTail; // An uncontended shadow copy of tail, owned by the consumer + + char cachelineFiller0[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic) - sizeof(size_t)]; + weak_atomic tail; // (Atomic) Elements are enqueued here + size_t localFront; + + char cachelineFiller1[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic) - sizeof(size_t)]; // next isn't very contended, but we don't want it on the same cache line as tail (which is) + weak_atomic next; // (Atomic) + + char* data; // Contents (on heap) are aligned to T's alignment + + const size_t sizeMask; + + + // size must be a power of two (and greater than 0) + AE_NO_TSAN Block(size_t const& _size, char* _rawThis, char* _data) + : front(0UL), localTail(0), tail(0UL), localFront(0), next(nullptr), data(_data), sizeMask(_size - 1), rawThis(_rawThis) + { + } + + private: + // C4512 - Assignment operator could not be generated + Block& operator=(Block const&); + + public: + char* rawThis; + }; + + + static Block* make_block(size_t capacity) AE_NO_TSAN + { + // Allocate enough memory for the block itself, as well as all the elements it will contain + auto size = sizeof(Block) + std::alignment_of::value - 1; + size += sizeof(T) * capacity + std::alignment_of::value - 1; + auto newBlockRaw = static_cast(std::malloc(size)); + if (newBlockRaw == nullptr) { + return nullptr; + } + + auto newBlockAligned = align_for(newBlockRaw); + auto newBlockData = align_for(newBlockAligned + sizeof(Block)); + return new (newBlockAligned) Block(capacity, newBlockRaw, newBlockData); + } + +private: + weak_atomic frontBlock; // (Atomic) Elements are dequeued from this block + + char cachelineFiller[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic)]; + weak_atomic tailBlock; // (Atomic) Elements are enqueued to this block + + size_t largestBlockSize; + +#ifndef NDEBUG + weak_atomic enqueuing; + mutable weak_atomic dequeuing; +#endif +}; + +// Like ReaderWriterQueue, but also providees blocking operations +template +class BlockingReaderWriterQueue +{ +private: + typedef ::moodycamel::ReaderWriterQueue ReaderWriterQueue; + +public: + explicit BlockingReaderWriterQueue(size_t size = 15) AE_NO_TSAN + : inner(size), sema(new spsc_sema::LightweightSemaphore()) + { } + + BlockingReaderWriterQueue(BlockingReaderWriterQueue&& other) AE_NO_TSAN + : inner(std::move(other.inner)), sema(std::move(other.sema)) + { } + + BlockingReaderWriterQueue& operator=(BlockingReaderWriterQueue&& other) AE_NO_TSAN + { + std::swap(sema, other.sema); + std::swap(inner, other.inner); + return *this; + } + + + // Enqueues a copy of element if there is room in the queue. + // Returns true if the element was enqueued, false otherwise. + // Does not allocate memory. + AE_FORCEINLINE bool try_enqueue(T const& element) AE_NO_TSAN + { + if (inner.try_enqueue(element)) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a moved copy of element if there is room in the queue. + // Returns true if the element was enqueued, false otherwise. + // Does not allocate memory. + AE_FORCEINLINE bool try_enqueue(T&& element) AE_NO_TSAN + { + if (inner.try_enqueue(std::forward(element))) { + sema->signal(); + return true; + } + return false; + } + +#if MOODYCAMEL_HAS_EMPLACE + // Like try_enqueue() but with emplace semantics (i.e. construct-in-place). + template + AE_FORCEINLINE bool try_emplace(Args&&... args) AE_NO_TSAN + { + if (inner.try_emplace(std::forward(args)...)) { + sema->signal(); + return true; + } + return false; + } +#endif + + + // Enqueues a copy of element on the queue. + // Allocates an additional block of memory if needed. + // Only fails (returns false) if memory allocation fails. + AE_FORCEINLINE bool enqueue(T const& element) AE_NO_TSAN + { + if (inner.enqueue(element)) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a moved copy of element on the queue. + // Allocates an additional block of memory if needed. + // Only fails (returns false) if memory allocation fails. + AE_FORCEINLINE bool enqueue(T&& element) AE_NO_TSAN + { + if (inner.enqueue(std::forward(element))) { + sema->signal(); + return true; + } + return false; + } + +#if MOODYCAMEL_HAS_EMPLACE + // Like enqueue() but with emplace semantics (i.e. construct-in-place). + template + AE_FORCEINLINE bool emplace(Args&&... args) AE_NO_TSAN + { + if (inner.emplace(std::forward(args)...)) { + sema->signal(); + return true; + } + return false; + } +#endif + + + // Attempts to dequeue an element; if the queue is empty, + // returns false instead. If the queue has at least one element, + // moves front to result using operator=, then returns true. + template + bool try_dequeue(U& result) AE_NO_TSAN + { + if (sema->tryWait()) { + bool success = inner.try_dequeue(result); + assert(success); + AE_UNUSED(success); + return true; + } + return false; + } + + + // Attempts to dequeue an element; if the queue is empty, + // waits until an element is available, then dequeues it. + template + void wait_dequeue(U& result) AE_NO_TSAN + { + while (!sema->wait()); + bool success = inner.try_dequeue(result); + AE_UNUSED(result); + assert(success); + AE_UNUSED(success); + } + + + // Attempts to dequeue an element; if the queue is empty, + // waits until an element is available up to the specified timeout, + // then dequeues it and returns true, or returns false if the timeout + // expires before an element can be dequeued. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue. + template + bool wait_dequeue_timed(U& result, std::int64_t timeout_usecs) AE_NO_TSAN + { + if (!sema->wait(timeout_usecs)) { + return false; + } + bool success = inner.try_dequeue(result); + AE_UNUSED(result); + assert(success); + AE_UNUSED(success); + return true; + } + + +#if __cplusplus > 199711L || _MSC_VER >= 1700 + // Attempts to dequeue an element; if the queue is empty, + // waits until an element is available up to the specified timeout, + // then dequeues it and returns true, or returns false if the timeout + // expires before an element can be dequeued. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue. + template + inline bool wait_dequeue_timed(U& result, std::chrono::duration const& timeout) AE_NO_TSAN + { + return wait_dequeue_timed(result, std::chrono::duration_cast(timeout).count()); + } +#endif + + + // Returns a pointer to the front element in the queue (the one that + // would be removed next by a call to `try_dequeue` or `pop`). If the + // queue appears empty at the time the method is called, nullptr is + // returned instead. + // Must be called only from the consumer thread. + AE_FORCEINLINE T* peek() const AE_NO_TSAN + { + return inner.peek(); + } + + // Removes the front element from the queue, if any, without returning it. + // Returns true on success, or false if the queue appeared empty at the time + // `pop` was called. + AE_FORCEINLINE bool pop() AE_NO_TSAN + { + if (sema->tryWait()) { + bool result = inner.pop(); + assert(result); + AE_UNUSED(result); + return true; + } + return false; + } + + // Returns the approximate number of items currently in the queue. + // Safe to call from both the producer and consumer threads. + AE_FORCEINLINE size_t size_approx() const AE_NO_TSAN + { + return sema->availableApprox(); + } + + // Returns the total number of items that could be enqueued without incurring + // an allocation when this queue is empty. + // Safe to call from both the producer and consumer threads. + // + // NOTE: The actual capacity during usage may be different depending on the consumer. + // If the consumer is removing elements concurrently, the producer cannot add to + // the block the consumer is removing from until it's completely empty, except in + // the case where the producer was writing to the same block the consumer was + // reading from the whole time. + AE_FORCEINLINE size_t max_capacity() const { + return inner.max_capacity(); + } + +private: + // Disable copying & assignment + BlockingReaderWriterQueue(BlockingReaderWriterQueue const&) { } + BlockingReaderWriterQueue& operator=(BlockingReaderWriterQueue const&) { } + +private: + ReaderWriterQueue inner; + std::unique_ptr sema; +}; + +} // end namespace moodycamel + +#ifdef AE_VCPP +#pragma warning(pop) +#endif diff --git a/External/readerwriterqueue/readerwriterqueueConfig.cmake.in b/External/readerwriterqueue/readerwriterqueueConfig.cmake.in new file mode 100644 index 0000000..b8fad19 --- /dev/null +++ b/External/readerwriterqueue/readerwriterqueueConfig.cmake.in @@ -0,0 +1,3 @@ +@PACKAGE_INIT@ + +include(${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake) diff --git a/External/readerwriterqueue/tests/common/simplethread.cpp b/External/readerwriterqueue/tests/common/simplethread.cpp new file mode 100644 index 0000000..ac8c49a --- /dev/null +++ b/External/readerwriterqueue/tests/common/simplethread.cpp @@ -0,0 +1,82 @@ +#include "simplethread.h" + +#if defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#include + +struct SimpleThread::ThreadRef +{ + HANDLE handle; + + static DWORD WINAPI ThreadProc(LPVOID param) + { + auto threadRef = static_cast(param); + threadRef->callbackFunc(threadRef->callbackObj); + return 0; + } + + ThreadRef(void* callbackObj, CallbackFunc callbackFunc) + : callbackObj(callbackObj), callbackFunc(callbackFunc) + { + } + + void* callbackObj; + CallbackFunc callbackFunc; +}; + +void SimpleThread::startThread(void* callbackObj, CallbackFunc callbackFunc) +{ + thread = new ThreadRef(callbackObj, callbackFunc); + thread->handle = CreateThread(NULL, StackSize, &ThreadRef::ThreadProc, thread, 0, NULL); +} + +void SimpleThread::join() +{ + if (thread != nullptr && thread->handle != NULL) { + WaitForSingleObject(thread->handle, INFINITE); + CloseHandle(thread->handle); + thread->handle = NULL; + } +} +#else +#include + +struct SimpleThread::ThreadRef +{ + std::thread thread; + + static void threadProc(ThreadRef* threadRef) + { + threadRef->callbackFunc(threadRef->callbackObj); + } + + ThreadRef(void* callbackObj, CallbackFunc callbackFunc) + : callbackObj(callbackObj), callbackFunc(callbackFunc) + { + } + + void* callbackObj; + CallbackFunc callbackFunc; +}; + +void SimpleThread::startThread(void* callbackObj, CallbackFunc callbackFunc) +{ + thread = new ThreadRef(callbackObj, callbackFunc); + thread->thread = std::thread(&ThreadRef::threadProc, thread); +} + +void SimpleThread::join() +{ + if (thread != nullptr && thread->thread.joinable()) { + thread->thread.join(); + } +} +#endif + +SimpleThread::~SimpleThread() +{ + if (thread != nullptr) { + join(); + delete thread; + } +} diff --git a/External/readerwriterqueue/tests/common/simplethread.h b/External/readerwriterqueue/tests/common/simplethread.h new file mode 100644 index 0000000..c8ed709 --- /dev/null +++ b/External/readerwriterqueue/tests/common/simplethread.h @@ -0,0 +1,154 @@ +#pragma once + +// Like C++11's std::thread, but with a reduced API, and works on Windows with MSVC2010+. +// Wraps std::thread on other OSes. Perhaps the most significant departure between +// std::thread and this mini-library is that join() is called implicitly in the destructor, +// if the thread is joinable. The thread callback functions should not throw exceptions. + +#include +#include + + +namespace details +{ + template + struct ArgWrapper + { + typename std::remove_reference::type arg1; + typename std::remove_reference::type arg2; + typename std::remove_reference::type arg3; + template + ArgWrapper(T&& a1, U&& a2, V&& a3) : arg1(std::forward(a1)), arg2(std::forward(a2)), arg3(std::forward(a3)) { } + template + void callCallback(TCallback&& callback) const { std::forward(callback)(std::move(arg1), std::move(arg2), std::move(arg3)); } + }; + + template + struct ArgWrapper + { + typename std::remove_reference::type arg1; + typename std::remove_reference::type arg2; + template + ArgWrapper(T&& a1, U&& a2) : arg1(std::forward(a1)), arg2(std::forward(a2)) { } + template + void callCallback(TCallback&& callback) const { std::forward(callback)(std::move(arg1), std::move(arg2)); } + }; + + template + struct ArgWrapper + { + typename std::remove_reference::type arg1; + template + ArgWrapper(T&& a1) : arg1(std::forward(a1)) { } + template + void callCallback(TCallback&& callback) const { std::forward(callback)(std::move(arg1)); } + }; + + template<> struct ArgWrapper + { + template void callCallback(TCallback&& callback) const { std::forward(callback)(); } + }; +} + + +class SimpleThread +{ +private: + struct ThreadRef; + + template + struct CallbackWrapper + { + template + CallbackWrapper(TCallback&& callback, U&& args) + : callback(std::forward(callback)), args(std::forward(args)) + { + } + + static void callAndDelete(void* wrapper) + { + auto typedWrapper = static_cast(wrapper); + typedWrapper->args.callCallback(std::move(typedWrapper->callback)); + delete typedWrapper; + } + + typename std::decay::type callback; + TArgs args; + }; + + typedef void (*CallbackFunc)(void*); + + void startThread(void* callbackObj, CallbackFunc callbackFunc); + + +public: + static const int StackSize = 4 * 1024; // bytes + + SimpleThread() : thread(nullptr) { } + + SimpleThread(SimpleThread&& other) + : thread(other.thread) + { + other.thread = nullptr; + } + + SimpleThread& operator=(SimpleThread&& other) + { + thread = other.thread; + other.thread = nullptr; + return *this; + } + + // Disable copying and copy-assignment +private: + SimpleThread(SimpleThread const&); + SimpleThread& operator=(SimpleThread const&); +public: + + template + explicit SimpleThread(TCallback&& callback) + { + auto wrapper = new CallbackWrapper>( + std::forward(callback), + details::ArgWrapper<>() + ); + startThread(wrapper, &CallbackWrapper>::callAndDelete); + } + + template + explicit SimpleThread(TCallback&& callback, TArg1&& arg1) + { + auto wrapper = new CallbackWrapper>( + std::forward(callback), + details::ArgWrapper(std::forward(arg1)) + ); + startThread(wrapper, &CallbackWrapper>::callAndDelete); + } + + template + explicit SimpleThread(TCallback&& callback, TArg1&& arg1, TArg2&& arg2) + { + auto wrapper = new CallbackWrapper>( + std::forward(callback), + details::ArgWrapper(std::forward(arg1), std::forward(arg2)) + ); + startThread(wrapper, &CallbackWrapper>::callAndDelete); + } + + template + explicit SimpleThread(TCallback&& callback, TArg1&& arg1, TArg2&& arg2, TArg3&& arg3) + { + auto wrapper = new CallbackWrapper>( + std::forward(callback), + details::ArgWrapper(std::forward(arg1), std::forward(arg2), std::forward(arg3)) + ); + startThread(wrapper, &CallbackWrapper>::callAndDelete); + } + + ~SimpleThread(); + + void join(); + +private: + ThreadRef* thread; +}; diff --git a/External/readerwriterqueue/tests/stabtest/makefile b/External/readerwriterqueue/tests/stabtest/makefile new file mode 100644 index 0000000..13795ab --- /dev/null +++ b/External/readerwriterqueue/tests/stabtest/makefile @@ -0,0 +1,24 @@ +ifeq ($(OS),Windows_NT) + EXT=.exe + PLATFORM_OPTS=-static + PLATFORM_LD_OPTS=-Wl,--no-as-needed +else + UNAME_S := $(shell uname -s) + ifeq ($(UNAME_S),Darwin) + EXT= + PLATFORM_OPTS= + PLATFORM_LD_OPTS= + else + EXT= + PLATFORM_OPTS= + PLATFORM_LD_OPTS=-lrt -Wl,--no-as-needed + endif +endif + +default: stabtest$(EXT) + +stabtest$(EXT): stabtest.cpp ../../readerwriterqueue.h ../../atomicops.h ../common/simplethread.h ../common/simplethread.cpp makefile + g++ $(PLATFORM_OPTS) -std=c++11 -Wsign-conversion -Wpedantic -Wall -DNDEBUG -O3 stabtest.cpp ../common/simplethread.cpp -o stabtest$(EXT) -pthread $(PLATFORM_LD_OPTS) + +run: stabtest$(EXT) + ./stabtest$(EXT) diff --git a/External/readerwriterqueue/tests/stabtest/msvc10/stabtest.sln b/External/readerwriterqueue/tests/stabtest/msvc10/stabtest.sln new file mode 100644 index 0000000..b8d7f06 --- /dev/null +++ b/External/readerwriterqueue/tests/stabtest/msvc10/stabtest.sln @@ -0,0 +1,26 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "stabtest", "stabtest.vcxproj", "{16E74A53-972D-4762-BC18-8946FB1EF452}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {16E74A53-972D-4762-BC18-8946FB1EF452}.Debug|Win32.ActiveCfg = Debug|Win32 + {16E74A53-972D-4762-BC18-8946FB1EF452}.Debug|Win32.Build.0 = Debug|Win32 + {16E74A53-972D-4762-BC18-8946FB1EF452}.Debug|x64.ActiveCfg = Debug|x64 + {16E74A53-972D-4762-BC18-8946FB1EF452}.Debug|x64.Build.0 = Debug|x64 + {16E74A53-972D-4762-BC18-8946FB1EF452}.Release|Win32.ActiveCfg = Release|Win32 + {16E74A53-972D-4762-BC18-8946FB1EF452}.Release|Win32.Build.0 = Release|Win32 + {16E74A53-972D-4762-BC18-8946FB1EF452}.Release|x64.ActiveCfg = Release|x64 + {16E74A53-972D-4762-BC18-8946FB1EF452}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/External/readerwriterqueue/tests/stabtest/msvc10/stabtest.vcxproj b/External/readerwriterqueue/tests/stabtest/msvc10/stabtest.vcxproj new file mode 100644 index 0000000..721df9b --- /dev/null +++ b/External/readerwriterqueue/tests/stabtest/msvc10/stabtest.vcxproj @@ -0,0 +1,157 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {16E74A53-972D-4762-BC18-8946FB1EF452} + Win32Proj + stabtest + + + + Application + true + Unicode + + + Application + true + Unicode + + + Application + false + true + Unicode + + + Application + false + true + Unicode + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Configuration)\$(Platform) + obj\$(Configuration)\$(Platform) + + + true + $(SolutionDir)$(Configuration)\$(Platform) + obj\$(Configuration)\$(Platform) + + + false + $(SolutionDir)$(Configuration)\$(Platform) + obj\$(Configuration)\$(Platform) + + + false + $(SolutionDir)$(Configuration)\$(Platform) + obj\$(Configuration)\$(Platform) + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/External/readerwriterqueue/tests/stabtest/msvc10/stabtest.vcxproj.filters b/External/readerwriterqueue/tests/stabtest/msvc10/stabtest.vcxproj.filters new file mode 100644 index 0000000..d3aeaf5 --- /dev/null +++ b/External/readerwriterqueue/tests/stabtest/msvc10/stabtest.vcxproj.filters @@ -0,0 +1,36 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/External/readerwriterqueue/tests/stabtest/msvc12/stabtest.sln b/External/readerwriterqueue/tests/stabtest/msvc12/stabtest.sln new file mode 100644 index 0000000..1e4dcc7 --- /dev/null +++ b/External/readerwriterqueue/tests/stabtest/msvc12/stabtest.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.30501.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "stabtest", "stabtest.vcxproj", "{16E74A53-972D-4762-BC18-8946FB1EF452}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {16E74A53-972D-4762-BC18-8946FB1EF452}.Debug|Win32.ActiveCfg = Debug|Win32 + {16E74A53-972D-4762-BC18-8946FB1EF452}.Debug|Win32.Build.0 = Debug|Win32 + {16E74A53-972D-4762-BC18-8946FB1EF452}.Debug|x64.ActiveCfg = Debug|x64 + {16E74A53-972D-4762-BC18-8946FB1EF452}.Debug|x64.Build.0 = Debug|x64 + {16E74A53-972D-4762-BC18-8946FB1EF452}.Release|Win32.ActiveCfg = Release|Win32 + {16E74A53-972D-4762-BC18-8946FB1EF452}.Release|Win32.Build.0 = Release|Win32 + {16E74A53-972D-4762-BC18-8946FB1EF452}.Release|x64.ActiveCfg = Release|x64 + {16E74A53-972D-4762-BC18-8946FB1EF452}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/External/readerwriterqueue/tests/stabtest/msvc12/stabtest.vcxproj b/External/readerwriterqueue/tests/stabtest/msvc12/stabtest.vcxproj new file mode 100644 index 0000000..3ac486f --- /dev/null +++ b/External/readerwriterqueue/tests/stabtest/msvc12/stabtest.vcxproj @@ -0,0 +1,161 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {16E74A53-972D-4762-BC18-8946FB1EF452} + Win32Proj + stabtest + + + + Application + true + Unicode + v120 + + + Application + true + Unicode + v120 + + + Application + false + true + Unicode + v120 + + + Application + false + true + Unicode + v120 + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Configuration)\$(Platform) + obj\$(Configuration)\$(Platform) + + + true + $(SolutionDir)$(Configuration)\$(Platform) + obj\$(Configuration)\$(Platform) + + + false + $(SolutionDir)$(Configuration)\$(Platform) + obj\$(Configuration)\$(Platform) + + + false + $(SolutionDir)$(Configuration)\$(Platform) + obj\$(Configuration)\$(Platform) + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/External/readerwriterqueue/tests/stabtest/msvc12/stabtest.vcxproj.filters b/External/readerwriterqueue/tests/stabtest/msvc12/stabtest.vcxproj.filters new file mode 100644 index 0000000..d3aeaf5 --- /dev/null +++ b/External/readerwriterqueue/tests/stabtest/msvc12/stabtest.vcxproj.filters @@ -0,0 +1,36 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/External/readerwriterqueue/tests/stabtest/stabtest.cpp b/External/readerwriterqueue/tests/stabtest/stabtest.cpp new file mode 100644 index 0000000..1e91fe7 --- /dev/null +++ b/External/readerwriterqueue/tests/stabtest/stabtest.cpp @@ -0,0 +1,80 @@ +#include "../../readerwriterqueue.h" +#include "../common/simplethread.h" + +using namespace moodycamel; + +#include +#include +#include +#include // rand() +//#include // usleep() + +void unpredictableDelay(int extra = 0) +{ +/* if ((rand() & 4095) == 0) { + usleep(2000 + extra); // in microseconds + }*/ +} + +int main(int argc, char** argv) +{ + // Disable buffering (so that when run in, e.g., Sublime Text, the output appears as it is written) + std::setvbuf(stdout, nullptr, _IONBF, 0); + + std::printf("Running stability test for moodycamel::ReaderWriterQueue.\n"); + std::printf("Logging to 'log.txt'. Press CTRL+C to quit.\n\n"); + + + std::ofstream log("log.txt"); + + try { + for (unsigned int i = 0; true; ++i) { + log << "Test #" << i << std::endl; + std::printf("Test #%d\n", i); + + ReaderWriterQueue q((rand() % 32) + 1); + + SimpleThread writer([&]() { + for (unsigned long long j = 0; j < 1024ULL * 1024ULL * 32ULL; ++j) { + unpredictableDelay(500); + q.enqueue(j); + } + }); + + SimpleThread reader([&]() { + bool canLog = true; + unsigned long long element; + for (unsigned long long j = 0; j < 1024ULL * 1024ULL * 32ULL;) { + if (canLog && (j & (1024 * 1024 * 16 - 1)) == 0) { + log << " ... iteration " << j << std::endl; + std::printf(" ... iteration %llu\n", j); + canLog = false; + } + unpredictableDelay(); + if (q.try_dequeue(element)) { + if (element != j) { + log << " ERROR DETECTED: Expected to read " << j << " but found " << element << std::endl; + std::printf(" ERROR DETECTED: Expected to read %llu but found %llu", j, element); + } + ++j; + canLog = true; + } + } + if (q.try_dequeue(element)) { + log << " ERROR DETECTED: Expected queue to be empty" << std::endl; + std::printf(" ERROR DETECTED: Expected queue to be empty\n"); + } + }); + + writer.join(); + reader.join(); + } + } + catch (std::exception const& ex) { + log << " ERROR DETECTED: Exception thrown: " << ex.what() << std::endl; + std::printf(" ERROR DETECTED: Exception thrown: %s\n", ex.what()); + } + + return 0; +} + diff --git a/External/readerwriterqueue/tests/unittests/makefile b/External/readerwriterqueue/tests/unittests/makefile new file mode 100644 index 0000000..f681418 --- /dev/null +++ b/External/readerwriterqueue/tests/unittests/makefile @@ -0,0 +1,27 @@ + + +ifeq ($(OS),Windows_NT) + EXT=.exe + PLATFORM_OPTS=-static + PLATFORM_LD_OPTS=-Wl,--no-as-needed +else + UNAME_S := $(shell uname -s) + ifeq ($(UNAME_S),Darwin) + EXT= + PLATFORM_OPTS= + PLATFORM_LD_OPTS= + else + EXT= + PLATFORM_OPTS= + PLATFORM_LD_OPTS=-lrt -Wl,--no-as-needed + endif +endif + + +default: unittests$(EXT) + +unittests$(EXT): unittests.cpp ../../readerwriterqueue.h ../../readerwritercircularbuffer.h ../../atomicops.h ../common/simplethread.h ../common/simplethread.cpp minitest.h makefile + g++ $(PLATFORM_OPTS) -std=c++11 -Wsign-conversion -Wpedantic -Wall -DNDEBUG -O3 -g unittests.cpp ../common/simplethread.cpp -o unittests$(EXT) -pthread $(PLATFORM_LD_OPTS) + +run: unittests$(EXT) + ./unittests$(EXT) diff --git a/External/readerwriterqueue/tests/unittests/minitest.h b/External/readerwriterqueue/tests/unittests/minitest.h new file mode 100644 index 0000000..1f7b872 --- /dev/null +++ b/External/readerwriterqueue/tests/unittests/minitest.h @@ -0,0 +1,125 @@ +// ©2013-2014 Cameron Desrochers. +// Distributed under the simplified BSD license (see the LICENSE file that +// should have come with this header). + +// Provides an extremely basic unit testing framework. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#ifdef __GNUG__ +#include +#include +#endif + + + +#define REGISTER_TEST(testName) registerTest(#testName, &subclass_t::testName) + +#define ASSERT_OR_FAIL(expr) { if (!(expr)) { notifyTestFailed(__LINE__, #expr); return false; } } +#define SUCCEED() { return true; } + + + +// Uses CRTP +template +class TestClass +{ +public: + static void notifyTestFailed(int line, const char* expr) + { + std::printf(" FAILED!\n ******* Assertion failed (line %d): %s\n\n", line, expr); + } + + bool validateTestName(std::string const& which) const + { + return testMap.find(which) != testMap.end(); + } + + void getAllTestNames(std::vector& names) const + { + for (auto it = testMap.cbegin(); it != testMap.cend(); ++it) { + names.push_back(it->first); + } + } + + bool run(unsigned int iterations = 1) + { + bool success = true; + for (auto it = testVec.cbegin(); it != testVec.cend(); ++it) { + if (!execTest(*it, iterations)) { + success = false; + } + } + return success; + } + + bool run(std::vector const& which, unsigned int iterations = 1) + { + bool success = true; + for (auto it = which.begin(); it != which.end(); ++it) { + if (!execTest(*testMap.find(*it), iterations)) { + success = false; + } + } + return success; + } + +protected: + typedef TSubclass subclass_t; + + void registerTest(const char* name, bool (subclass_t::* method)()) + { + testVec.push_back(std::make_pair(std::string(name), method)); + testMap[std::string(name)] = method; + } + + bool execTest(std::pair const& testRef, unsigned int iterations) + { + std::printf("%s::%s... \n", demangle_type_name(typeid(subclass_t).name()).c_str(), testRef.first.c_str()); + + bool result = true; + for (unsigned int i = 0; i != iterations; ++i) { + if (!(static_cast(this)->*testRef.second)()) { + result = false; + break; + } + } + + if (result) { + std::printf(" passed\n\n"); + } + else { + std::printf(" FAILED!\n\n"); + } + return result; + } + +private: + static std::string demangle_type_name(const char* name) + { +#ifdef __GNUG__ + // Adapted from http://stackoverflow.com/a/4541470/21475 + int status = -4; + char* res = abi::__cxa_demangle(name, nullptr, nullptr, &status); + + const char* const demangled_name = (status == 0) ? res : name; + std::string ret(demangled_name); + + std::free(res); + return ret; +#else + return name; +#endif + } + +protected: + std::vector > testVec; + std::map testMap; +}; diff --git a/External/readerwriterqueue/tests/unittests/msvc10/unittests.sln b/External/readerwriterqueue/tests/unittests/msvc10/unittests.sln new file mode 100644 index 0000000..d3d21ae --- /dev/null +++ b/External/readerwriterqueue/tests/unittests/msvc10/unittests.sln @@ -0,0 +1,26 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "unittests", "unittests.vcxproj", "{C209657D-56BF-4A61-8FD2-DBAEB1E51B3B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C209657D-56BF-4A61-8FD2-DBAEB1E51B3B}.Debug|Win32.ActiveCfg = Debug|Win32 + {C209657D-56BF-4A61-8FD2-DBAEB1E51B3B}.Debug|Win32.Build.0 = Debug|Win32 + {C209657D-56BF-4A61-8FD2-DBAEB1E51B3B}.Debug|x64.ActiveCfg = Debug|x64 + {C209657D-56BF-4A61-8FD2-DBAEB1E51B3B}.Debug|x64.Build.0 = Debug|x64 + {C209657D-56BF-4A61-8FD2-DBAEB1E51B3B}.Release|Win32.ActiveCfg = Release|Win32 + {C209657D-56BF-4A61-8FD2-DBAEB1E51B3B}.Release|Win32.Build.0 = Release|Win32 + {C209657D-56BF-4A61-8FD2-DBAEB1E51B3B}.Release|x64.ActiveCfg = Release|x64 + {C209657D-56BF-4A61-8FD2-DBAEB1E51B3B}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/External/readerwriterqueue/tests/unittests/msvc10/unittests.vcxproj b/External/readerwriterqueue/tests/unittests/msvc10/unittests.vcxproj new file mode 100644 index 0000000..2bddade --- /dev/null +++ b/External/readerwriterqueue/tests/unittests/msvc10/unittests.vcxproj @@ -0,0 +1,158 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {C209657D-56BF-4A61-8FD2-DBAEB1E51B3B} + Win32Proj + unittests + + + + Application + true + Unicode + + + Application + true + Unicode + + + Application + false + true + Unicode + + + Application + false + true + Unicode + + + + + + + + + + + + + + + + + + + true + obj\$(Configuration)\$(Platform)\ + $(SolutionDir)$(Configuration)\$(Platform)\ + + + true + obj\$(Configuration)\$(Platform)\ + $(SolutionDir)$(Configuration)\$(Platform)\ + + + false + obj\$(Configuration)\$(Platform)\ + $(SolutionDir)$(Configuration)\$(Platform)\ + + + false + obj\$(Configuration)\$(Platform)\ + $(SolutionDir)$(Configuration)\$(Platform)\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/External/readerwriterqueue/tests/unittests/msvc10/unittests.vcxproj.filters b/External/readerwriterqueue/tests/unittests/msvc10/unittests.vcxproj.filters new file mode 100644 index 0000000..ac6c442 --- /dev/null +++ b/External/readerwriterqueue/tests/unittests/msvc10/unittests.vcxproj.filters @@ -0,0 +1,39 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/External/readerwriterqueue/tests/unittests/msvc12/unittests.sln b/External/readerwriterqueue/tests/unittests/msvc12/unittests.sln new file mode 100644 index 0000000..715e35e --- /dev/null +++ b/External/readerwriterqueue/tests/unittests/msvc12/unittests.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.30501.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "unittests", "unittests.vcxproj", "{C209657D-56BF-4A61-8FD2-DBAEB1E51B3B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C209657D-56BF-4A61-8FD2-DBAEB1E51B3B}.Debug|Win32.ActiveCfg = Debug|Win32 + {C209657D-56BF-4A61-8FD2-DBAEB1E51B3B}.Debug|Win32.Build.0 = Debug|Win32 + {C209657D-56BF-4A61-8FD2-DBAEB1E51B3B}.Debug|x64.ActiveCfg = Debug|x64 + {C209657D-56BF-4A61-8FD2-DBAEB1E51B3B}.Debug|x64.Build.0 = Debug|x64 + {C209657D-56BF-4A61-8FD2-DBAEB1E51B3B}.Release|Win32.ActiveCfg = Release|Win32 + {C209657D-56BF-4A61-8FD2-DBAEB1E51B3B}.Release|Win32.Build.0 = Release|Win32 + {C209657D-56BF-4A61-8FD2-DBAEB1E51B3B}.Release|x64.ActiveCfg = Release|x64 + {C209657D-56BF-4A61-8FD2-DBAEB1E51B3B}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/External/readerwriterqueue/tests/unittests/msvc12/unittests.vcxproj b/External/readerwriterqueue/tests/unittests/msvc12/unittests.vcxproj new file mode 100644 index 0000000..da04821 --- /dev/null +++ b/External/readerwriterqueue/tests/unittests/msvc12/unittests.vcxproj @@ -0,0 +1,162 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {C209657D-56BF-4A61-8FD2-DBAEB1E51B3B} + Win32Proj + unittests + + + + Application + true + Unicode + v120 + + + Application + true + Unicode + v120 + + + Application + false + true + Unicode + v120 + + + Application + false + true + Unicode + v120 + + + + + + + + + + + + + + + + + + + true + obj\$(Configuration)\$(Platform)\ + $(SolutionDir)$(Configuration)\$(Platform)\ + + + true + obj\$(Configuration)\$(Platform)\ + $(SolutionDir)$(Configuration)\$(Platform)\ + + + false + obj\$(Configuration)\$(Platform)\ + $(SolutionDir)$(Configuration)\$(Platform)\ + + + false + obj\$(Configuration)\$(Platform)\ + $(SolutionDir)$(Configuration)\$(Platform)\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/External/readerwriterqueue/tests/unittests/msvc12/unittests.vcxproj.filters b/External/readerwriterqueue/tests/unittests/msvc12/unittests.vcxproj.filters new file mode 100644 index 0000000..ac6c442 --- /dev/null +++ b/External/readerwriterqueue/tests/unittests/msvc12/unittests.vcxproj.filters @@ -0,0 +1,39 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/External/readerwriterqueue/tests/unittests/unittests.cpp b/External/readerwriterqueue/tests/unittests/unittests.cpp new file mode 100644 index 0000000..df6fa73 --- /dev/null +++ b/External/readerwriterqueue/tests/unittests/unittests.cpp @@ -0,0 +1,951 @@ +// ©2013-2015 Cameron Desrochers +// Unit tests for moodycamel::ReaderWriterQueue + +#include +#include +#include +#include +#include + +#include "minitest.h" +#include "../common/simplethread.h" +#include "../../readerwriterqueue.h" +#include "../../readerwritercircularbuffer.h" + +using namespace moodycamel; + + +// *NOT* thread-safe +struct Foo +{ + Foo() : copied(false) { id = _id()++; } + Foo(Foo const& other) : id(other.id), copied(true) { } + Foo(Foo&& other) : id(other.id), copied(other.copied) { other.copied = true; } + Foo& operator=(Foo&& other) + { + verify(); + id = other.id, copied = other.copied; + other.copied = true; + return *this; + } + ~Foo() { verify(); } + +private: + void verify() + { + if (copied) return; + if (id != _last_destroyed_id() + 1) { + _destroyed_in_order() = false; + } + _last_destroyed_id() = id; + ++_destroy_count(); + } + +public: + static void reset() { _destroy_count() = 0; _id() = 0; _destroyed_in_order() = true; _last_destroyed_id() = -1; } + static int destroy_count() { return _destroy_count(); } + static bool destroyed_in_order() { return _destroyed_in_order(); } + +private: + static int& _destroy_count() { static int c = 0; return c; } + static int& _id() { static int i = 0; return i; } + static bool& _destroyed_in_order() { static bool d = true; return d; } + static int& _last_destroyed_id() { static int i = -1; return i; } + + int id; + bool copied; +}; + + +#if MOODYCAMEL_HAS_EMPLACE +class UniquePtrWrapper +{ +public: + UniquePtrWrapper() = default; + UniquePtrWrapper(std::unique_ptr p) : m_p(std::move(p)) {} + int get_value() const { return *m_p; } + std::unique_ptr& get_ptr() { return m_p; } +private: + std::unique_ptr m_p; +}; +#endif + +/// Extracted from private static method of ReaderWriterQueue +static size_t ceilToPow2(size_t x) +{ + // From http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + --x; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + for (size_t i = 1; i < sizeof(size_t); i <<= 1) { + x |= x >> (i << 3); + } + ++x; + return x; +} + + +class ReaderWriterQueueTests : public TestClass +{ +public: + ReaderWriterQueueTests() + { + REGISTER_TEST(create_empty_queue); + REGISTER_TEST(enqueue_one); + REGISTER_TEST(enqueue_many); + REGISTER_TEST(nonempty_destroy); + REGISTER_TEST(try_enqueue); + REGISTER_TEST(try_dequeue); + REGISTER_TEST(peek); + REGISTER_TEST(pop); + REGISTER_TEST(size_approx); + REGISTER_TEST(max_capacity); + REGISTER_TEST(threaded); + REGISTER_TEST(blocking); + REGISTER_TEST(vector); +#if MOODYCAMEL_HAS_EMPLACE + REGISTER_TEST(emplace); + REGISTER_TEST(try_enqueue_fail_workaround); + REGISTER_TEST(try_emplace_fail); +#endif + REGISTER_TEST(blocking_circular_buffer); + } + + bool create_empty_queue() + { + { + ReaderWriterQueue q; + } + + { + ReaderWriterQueue q(1234); + } + + return true; + } + + bool enqueue_one() + { + int item; + + { + item = 0; + ReaderWriterQueue q(1); + q.enqueue(12345); + ASSERT_OR_FAIL(q.try_dequeue(item)); + ASSERT_OR_FAIL(item == 12345); + } + + { + item = 0; + ReaderWriterQueue q(1); + ASSERT_OR_FAIL(q.try_enqueue(12345)); + ASSERT_OR_FAIL(q.try_dequeue(item)); + ASSERT_OR_FAIL(item == 12345); + } + + return true; + } + + bool enqueue_many() + { + int item = -1; + + { + ReaderWriterQueue q(100); + for (int i = 0; i != 100; ++i) { + q.enqueue(i); + } + + for (int i = 0; i != 100; ++i) { + ASSERT_OR_FAIL(q.try_dequeue(item)); + ASSERT_OR_FAIL(item == i); + } + } + + { + ReaderWriterQueue q(100); + for (int i = 0; i != 1200; ++i) { + q.enqueue(i); + } + + for (int i = 0; i != 1200; ++i) { + ASSERT_OR_FAIL(q.try_dequeue(item)); + ASSERT_OR_FAIL(item == i); + } + } + + return true; + } + + bool nonempty_destroy() + { + // Some elements at beginning + Foo::reset(); + { + ReaderWriterQueue q(31); + for (int i = 0; i != 10; ++i) { + q.enqueue(Foo()); + } + ASSERT_OR_FAIL(Foo::destroy_count() == 0); + } + ASSERT_OR_FAIL(Foo::destroy_count() == 10); + ASSERT_OR_FAIL(Foo::destroyed_in_order()); + + // Entire block + Foo::reset(); + { + ReaderWriterQueue q(31); + for (int i = 0; i != 31; ++i) { + q.enqueue(Foo()); + } + ASSERT_OR_FAIL(Foo::destroy_count() == 0); + } + ASSERT_OR_FAIL(Foo::destroy_count() == 31); + ASSERT_OR_FAIL(Foo::destroyed_in_order()); + + // Multiple blocks + Foo::reset(); + { + ReaderWriterQueue q(31); + for (int i = 0; i != 94; ++i) { + q.enqueue(Foo()); + } + ASSERT_OR_FAIL(Foo::destroy_count() == 0); + } + ASSERT_OR_FAIL(Foo::destroy_count() == 94); + ASSERT_OR_FAIL(Foo::destroyed_in_order()); + + // Some elements in another block + Foo::reset(); + { + ReaderWriterQueue q(31); + Foo item; + for (int i = 0; i != 42; ++i) { + q.enqueue(Foo()); + } + ASSERT_OR_FAIL(Foo::destroy_count() == 0); + for (int i = 0; i != 31; ++i) { + ASSERT_OR_FAIL(q.try_dequeue(item)); + } + ASSERT_OR_FAIL(Foo::destroy_count() == 31); + } + ASSERT_OR_FAIL(Foo::destroy_count() == 43); + ASSERT_OR_FAIL(Foo::destroyed_in_order()); + + // Some elements in multiple blocks + Foo::reset(); + { + ReaderWriterQueue q(31); + Foo item; + for (int i = 0; i != 123; ++i) { + q.enqueue(Foo()); + } + for (int i = 0; i != 25; ++i) { + ASSERT_OR_FAIL(q.try_dequeue(item)); + } + for (int i = 0; i != 47; ++i) { + q.enqueue(Foo()); + } + for (int i = 0; i != 140; ++i) { + ASSERT_OR_FAIL(q.try_dequeue(item)); + } + for (int i = 0; i != 230; ++i) { + q.enqueue(Foo()); + } + for (int i = 0; i != 130; ++i) { + ASSERT_OR_FAIL(q.try_dequeue(item)); + } + for (int i = 0; i != 100; ++i) { + q.enqueue(Foo()); + } + } + ASSERT_OR_FAIL(Foo::destroy_count() == 501); + ASSERT_OR_FAIL(Foo::destroyed_in_order()); + + return true; + } + + bool try_enqueue() + { + ReaderWriterQueue q(31); + int item; + int size = 0; + + for (int i = 0; i < 10000; ++i) { + if ((rand() & 1) == 1) { + bool result = q.try_enqueue(i); + if (size == 31) { + ASSERT_OR_FAIL(!result); + } + else { + ASSERT_OR_FAIL(result); + ++size; + } + } + else { + bool result = q.try_dequeue(item); + if (size == 0) { + ASSERT_OR_FAIL(!result); + } + else { + ASSERT_OR_FAIL(result); + --size; + } + } + } + + return true; + } + + bool try_dequeue() + { + int item; + + { + ReaderWriterQueue q(1); + ASSERT_OR_FAIL(!q.try_dequeue(item)); + } + + { + ReaderWriterQueue q(10); + ASSERT_OR_FAIL(!q.try_dequeue(item)); + } + + return true; + } + + bool threaded() + { + weak_atomic result; + result = 1; + + ReaderWriterQueue q(100); + SimpleThread reader([&]() { + int item; + int prevItem = -1; + for (int i = 0; i != 1000000; ++i) { + if (q.try_dequeue(item)) { + if (item <= prevItem) { + result = 0; + } + prevItem = item; + } + } + }); + SimpleThread writer([&]() { + for (int i = 0; i != 1000000; ++i) { + if (((i >> 7) & 1) == 0) { + q.enqueue(i); + } + else { + q.try_enqueue(i); + } + } + }); + + writer.join(); + reader.join(); + + return result.load() == 1 ? true : false; + } + + bool peek() + { + weak_atomic result; + result = 1; + + ReaderWriterQueue q(100); + SimpleThread reader([&]() { + int item; + int prevItem = -1; + int* peeked; + for (int i = 0; i != 100000; ++i) { + peeked = q.peek(); + if (peeked != nullptr) { + if (q.try_dequeue(item)) { + if (item <= prevItem || item != *peeked) { + result = 0; + } + prevItem = item; + } + else { + result = 0; + } + } + } + }); + SimpleThread writer([&]() { + for (int i = 0; i != 100000; ++i) { + if (((i >> 7) & 1) == 0) { + q.enqueue(i); + } + else { + q.try_enqueue(i); + } + } + }); + + writer.join(); + reader.join(); + + return result.load() == 1 ? true : false; + } + + bool pop() + { + weak_atomic result; + result = 1; + + ReaderWriterQueue q(100); + SimpleThread reader([&]() { + int item; + int prevItem = -1; + int* peeked; + for (int i = 0; i != 100000; ++i) { + peeked = q.peek(); + if (peeked != nullptr) { + item = *peeked; + if (q.pop()) { + if (item <= prevItem) { + result = 0; + } + prevItem = item; + } + else { + result = 0; + } + } + } + }); + SimpleThread writer([&]() { + for (int i = 0; i != 100000; ++i) { + if (((i >> 7) & 1) == 0) { + q.enqueue(i); + } + else { + q.try_enqueue(i); + } + } + }); + + writer.join(); + reader.join(); + + return result.load() == 1 ? true : false; + } + + bool size_approx() + { + weak_atomic result; + weak_atomic front; + weak_atomic tail; + + result = 1; + front = 0; + tail = 0; + + ReaderWriterQueue q(10); + SimpleThread reader([&]() { + int item; + for (int i = 0; i != 100000; ++i) { + if (q.try_dequeue(item)) { + fence(memory_order_release); + front = front.load() + 1; + } + int size = static_cast(q.size_approx()); + fence(memory_order_acquire); + int tail_ = tail.load(); + int front_ = front.load(); + if (size > tail_ - front_ || size < 0) { + result = 0; + } + } + }); + SimpleThread writer([&]() { + for (int i = 0; i != 100000; ++i) { + tail = tail.load() + 1; + fence(memory_order_release); + q.enqueue(i); + int tail_ = tail.load(); + int front_ = front.load(); + fence(memory_order_acquire); + int size = static_cast(q.size_approx()); + if (size > tail_ - front_ || size < 0) { + result = 0; + } + } + }); + + writer.join(); + reader.join(); + + return result.load() == 1 ? true : false; + } + + bool max_capacity() + { + { + // this math for queue size estimation is only valid for q_size <= 256 + for (size_t q_size = 2; q_size < 256; ++q_size) { + ReaderWriterQueue q(q_size); + ASSERT_OR_FAIL(q.max_capacity() == ceilToPow2(q_size+1)-1); + + const size_t start_cap = q.max_capacity(); + for (size_t i = 0; i < start_cap+1; ++i) // fill 1 past capacity to resize + q.enqueue(i); + ASSERT_OR_FAIL(q.max_capacity() == 3*start_cap+1); + } + } + return true; + } + + bool blocking() + { + { + BlockingReaderWriterQueue q; + int item; + + q.enqueue(123); + ASSERT_OR_FAIL(q.try_dequeue(item)); + ASSERT_OR_FAIL(item == 123); + ASSERT_OR_FAIL(q.size_approx() == 0); + + q.enqueue(234); + ASSERT_OR_FAIL(q.size_approx() == 1); + ASSERT_OR_FAIL(*q.peek() == 234); + ASSERT_OR_FAIL(*q.peek() == 234); + ASSERT_OR_FAIL(q.pop()); + + ASSERT_OR_FAIL(q.try_enqueue(345)); + q.wait_dequeue(item); + ASSERT_OR_FAIL(item == 345); + ASSERT_OR_FAIL(!q.peek()); + ASSERT_OR_FAIL(q.size_approx() == 0); + ASSERT_OR_FAIL(!q.try_dequeue(item)); + } + + weak_atomic result; + result = 1; + + { + BlockingReaderWriterQueue q(100); + SimpleThread reader([&]() { + int item = -1; + int prevItem = -1; + for (int i = 0; i != 1000000; ++i) { + q.wait_dequeue(item); + if (item <= prevItem) { + result = 0; + } + prevItem = item; + } + }); + SimpleThread writer([&]() { + for (int i = 0; i != 1000000; ++i) { + q.enqueue(i); + } + }); + + writer.join(); + reader.join(); + + ASSERT_OR_FAIL(q.size_approx() == 0); + ASSERT_OR_FAIL(result.load()); + } + + { + BlockingReaderWriterQueue q(100); + SimpleThread reader([&]() { + int item = -1; + int prevItem = -1; + for (int i = 0; i != 1000000; ++i) { + if (!q.wait_dequeue_timed(item, 1000)) { + --i; + continue; + } + if (item <= prevItem) { + result = 0; + } + prevItem = item; + } + }); + SimpleThread writer([&]() { + for (int i = 0; i != 1000000; ++i) { + q.enqueue(i); + for (volatile int x = 0; x != 100; ++x); + } + }); + + writer.join(); + reader.join(); + + int item; + ASSERT_OR_FAIL(q.size_approx() == 0); + ASSERT_OR_FAIL(!q.wait_dequeue_timed(item, 0)); + ASSERT_OR_FAIL(!q.wait_dequeue_timed(item, 1)); + ASSERT_OR_FAIL(result.load()); + } + +#if MOODYCAMEL_HAS_EMPLACE + { + BlockingReaderWriterQueue q(100); + std::unique_ptr p { new int(123) }; + q.emplace(std::move(p)); + q.try_emplace(std::move(p)); + UniquePtrWrapper item; + ASSERT_OR_FAIL(q.wait_dequeue_timed(item, 0)); + ASSERT_OR_FAIL(item.get_value() == 123); + ASSERT_OR_FAIL(q.wait_dequeue_timed(item, 0)); + ASSERT_OR_FAIL(item.get_ptr() == nullptr); + ASSERT_OR_FAIL(q.size_approx() == 0); + } +#endif + + return true; + } + + bool vector() + { + { + std::vector> queues; + queues.push_back(ReaderWriterQueue()); + queues.emplace_back(); + + queues[0].enqueue(1); + queues[1].enqueue(2); + std::swap(queues[0], queues[1]); + + int item; + ASSERT_OR_FAIL(queues[0].try_dequeue(item)); + ASSERT_OR_FAIL(item == 2); + + ASSERT_OR_FAIL(queues[1].try_dequeue(item)); + ASSERT_OR_FAIL(item == 1); + } + + { + std::vector> queues; + queues.push_back(BlockingReaderWriterQueue()); + queues.emplace_back(); + + queues[0].enqueue(1); + queues[1].enqueue(2); + std::swap(queues[0], queues[1]); + + int item; + ASSERT_OR_FAIL(queues[0].try_dequeue(item)); + ASSERT_OR_FAIL(item == 2); + + queues[1].wait_dequeue(item); + ASSERT_OR_FAIL(item == 1); + } + return true; + } + +#if MOODYCAMEL_HAS_EMPLACE + bool emplace() + { + ReaderWriterQueue q(100); + std::unique_ptr p { new int(123) }; + q.emplace(std::move(p)); + UniquePtrWrapper item; + ASSERT_OR_FAIL(q.try_dequeue(item)); + ASSERT_OR_FAIL(item.get_value() == 123); + ASSERT_OR_FAIL(q.size_approx() == 0); + + return true; + } + + // This is what you have to do to try_enqueue() a movable type, and demonstrates why try_emplace() is useful + bool try_enqueue_fail_workaround() + { + ReaderWriterQueue q(0); + { + // A failed try_enqueue() will still delete p + std::unique_ptr p { new int(123) }; + q.try_enqueue(std::move(p)); + ASSERT_OR_FAIL(q.size_approx() == 0); + ASSERT_OR_FAIL(p == nullptr); + } + { + // Workaround isn't pretty and potentially expensive - use try_emplace() instead + std::unique_ptr p { new int(123) }; + UniquePtrWrapper w(std::move(p)); + q.try_enqueue(std::move(w)); + p = std::move(w.get_ptr()); + ASSERT_OR_FAIL(q.size_approx() == 0); + ASSERT_OR_FAIL(p != nullptr); + ASSERT_OR_FAIL(*p == 123); + } + + return true; + } + + bool try_emplace_fail() + { + ReaderWriterQueue q(0); + std::unique_ptr p { new int(123) }; + q.try_emplace(std::move(p)); + ASSERT_OR_FAIL(q.size_approx() == 0); + ASSERT_OR_FAIL(p != nullptr); + ASSERT_OR_FAIL(*p == 123); + + return true; + } +#endif + + bool blocking_circular_buffer() + { + { + // Basic enqueue + BlockingReaderWriterCircularBuffer q(65); + for (int iteration = 0; iteration != 128; ++iteration) { // check there's no problem with mismatch between nominal and allocated capacity + ASSERT_OR_FAIL(q.max_capacity() == 65); + ASSERT_OR_FAIL(q.size_approx() == 0); + ASSERT_OR_FAIL(!q.try_pop()); + ASSERT_OR_FAIL(q.try_enqueue(0)); + ASSERT_OR_FAIL(q.max_capacity() == 65); + ASSERT_OR_FAIL(q.size_approx() == 1); + ASSERT_OR_FAIL(*q.peek() == 0); + for (int i = 1; i != 65; ++i) + q.wait_enqueue(i); + ASSERT_OR_FAIL(q.size_approx() == 65); + ASSERT_OR_FAIL(!q.try_enqueue(65)); + + // Basic dequeue + int item; + ASSERT_OR_FAIL(q.try_dequeue(item)); + ASSERT_OR_FAIL(item == 0); + for (int i = 1; i != 65; ++i) { + q.wait_dequeue(item); + ASSERT_OR_FAIL(item == i); + } + ASSERT_OR_FAIL(!q.try_dequeue(item)); + ASSERT_OR_FAIL(!q.wait_dequeue_timed(item, 1)); + ASSERT_OR_FAIL(item == 64); + } + } + + { + // Zero capacity + BlockingReaderWriterCircularBuffer q(0); + ASSERT_OR_FAIL(q.max_capacity() == 0); + ASSERT_OR_FAIL(!q.try_enqueue(1)); + ASSERT_OR_FAIL(!q.wait_enqueue_timed(1, 0)); + } + + // Element lifetimes + Foo::reset(); + { + BlockingReaderWriterCircularBuffer q(31); + { + Foo item; + for (int i = 0; i != 23 + 32; ++i) { + ASSERT_OR_FAIL(q.try_enqueue(Foo())); + ASSERT_OR_FAIL(q.try_dequeue(item)); + } + ASSERT_OR_FAIL(Foo::destroy_count() == 23 + 32); + ASSERT_OR_FAIL(Foo::destroyed_in_order()); + } + Foo::reset(); + + { + Foo item; + for (int i = 0; i != 23 + 32; ++i) { + ASSERT_OR_FAIL(q.try_enqueue(Foo())); + item = std::move(*q.peek()); + ASSERT_OR_FAIL(q.try_pop()); + } + ASSERT_OR_FAIL(!q.peek()); + ASSERT_OR_FAIL(!q.try_pop()); + ASSERT_OR_FAIL(Foo::destroy_count() == 23 + 32); + ASSERT_OR_FAIL(Foo::destroyed_in_order()); + } + Foo::reset(); + + { + Foo item; + for (int i = 0; i != 10; ++i) + ASSERT_OR_FAIL(q.try_enqueue(Foo())); + ASSERT_OR_FAIL(q.size_approx() == 10); + ASSERT_OR_FAIL(Foo::destroy_count() == 0); + ASSERT_OR_FAIL(q.try_dequeue(item)); + ASSERT_OR_FAIL(q.size_approx() == 9); + ASSERT_OR_FAIL(Foo::destroy_count() == 1); + } + ASSERT_OR_FAIL(Foo::destroy_count() == 2); + ASSERT_OR_FAIL(Foo::destroyed_in_order()); + + BlockingReaderWriterCircularBuffer q2(std::move(q)); + ASSERT_OR_FAIL(q.size_approx() == 0); + ASSERT_OR_FAIL(q2.size_approx() == 9); + + BlockingReaderWriterCircularBuffer q3(2); + q3 = std::move(q2); + ASSERT_OR_FAIL(q.size_approx() == 0); + ASSERT_OR_FAIL(q2.size_approx() == 0); + ASSERT_OR_FAIL(q3.size_approx() == 9); + + q = std::move(q2); + ASSERT_OR_FAIL(q.size_approx() == 0); + ASSERT_OR_FAIL(q2.size_approx() == 0); + ASSERT_OR_FAIL(q3.size_approx() == 9); + ASSERT_OR_FAIL(Foo::destroy_count() == 2); + } + ASSERT_OR_FAIL(Foo::destroy_count() == 11); + ASSERT_OR_FAIL(Foo::destroyed_in_order()); + + weak_atomic result; + result = 1; + + { + // Threaded + BlockingReaderWriterCircularBuffer q(8); + SimpleThread reader([&]() { + int item; + for (int i = 0; i != 1000000; ++i) { + int* peeked = q.peek(); + if (peeked) { + item = *peeked; + if (peeked != q.peek() || !q.try_pop()) + result = 0; + } + else { + q.wait_dequeue(item); + } + if (item != i) + result = 0; + } + }); + SimpleThread writer([&]() { + for (int i = 0; i != 1000000; ++i) + q.wait_enqueue(i); + }); + + writer.join(); + reader.join(); + + ASSERT_OR_FAIL(q.size_approx() == 0); + ASSERT_OR_FAIL(result.load()); + } + + return true; + } +}; + + + +void printTests(ReaderWriterQueueTests const& tests) +{ + std::printf(" Supported tests are:\n"); + + std::vector names; + tests.getAllTestNames(names); + for (auto it = names.cbegin(); it != names.cend(); ++it) { + std::printf(" %s\n", it->c_str()); + } +} + + +// Basic test harness +int main(int argc, char** argv) +{ + bool disablePrompt = false; + std::vector selectedTests; + + // Disable buffering (so that when run in, e.g., Sublime Text, the output appears as it is written) + std::setvbuf(stdout, nullptr, _IONBF, 0); + + // Isolate the executable name + std::string progName = argv[0]; + auto slash = progName.find_last_of("/\\"); + if (slash != std::string::npos) { + progName = progName.substr(slash + 1); + } + + ReaderWriterQueueTests tests; + + // Parse command line options + if (argc == 1) { + std::printf("Running all unit tests for moodycamel::ReaderWriterQueue.\n(Run %s --help for other options.)\n\n", progName.c_str()); + } + else { + bool printHelp = false; + bool printedTests = false; + bool error = false; + for (int i = 1; i < argc; ++i) { + if (std::strcmp(argv[i], "--help") == 0) { + printHelp = true; + } + else if (std::strcmp(argv[i], "--disable-prompt") == 0) { + disablePrompt = true; + } + else if (std::strcmp(argv[i], "--run") == 0) { + if (i + 1 == argc || argv[i + 1][0] == '-') { + std::printf("Expected test name argument for --run option.\n"); + if (!printedTests) { + printTests(tests); + printedTests = true; + } + error = true; + continue; + } + + if (!tests.validateTestName(argv[++i])) { + std::printf("Unrecognized test '%s'.\n", argv[i]); + if (!printedTests) { + printTests(tests); + printedTests = true; + } + error = true; + continue; + } + + selectedTests.push_back(argv[i]); + } + else { + std::printf("Unrecognized option '%s'.\n", argv[i]); + error = true; + } + } + + if (error || printHelp) { + if (error) { + std::printf("\n"); + } + std::printf("%s\n Description: Runs unit tests for moodycamel::ReaderWriterQueue\n", progName.c_str()); + std::printf(" --help Prints this help blurb\n"); + std::printf(" --run test Runs only the specified test(s)\n"); + std::printf(" --disable-prompt Disables prompt before exit when the tests finish\n"); + return error ? -1 : 0; + } + } + + + int exitCode = 0; + + bool result; + if (selectedTests.size() > 0) { + result = tests.run(selectedTests); + } + else { + result = tests.run(); + } + + if (result) { + std::printf("All %stests passed.\n", (selectedTests.size() > 0 ? "selected " : "")); + } + else { + std::printf("Test(s) failed!\n"); + exitCode = 2; + } + + if (!disablePrompt) { + std::printf("Press ENTER to exit.\n"); + getchar(); + } + return exitCode; +} + diff --git a/External/reaper-plugins/reaper_plugin.h b/External/reaper-plugins/reaper_plugin.h new file mode 100644 index 0000000..c552193 --- /dev/null +++ b/External/reaper-plugins/reaper_plugin.h @@ -0,0 +1,1524 @@ +/*************************************** +*** REAPER Plug-in API +** +** Copyright (C) 2006-2015, Cockos Incorporated +** +** This software is provided 'as-is', without any express or implied +** warranty. In no event will the authors be held liable for any damages +** arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, +** including commercial applications, and to alter it and redistribute it +** freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not +** claim that you wrote the original software. If you use this software +** in a product, an acknowledgment in the product documentation would be +** appreciated but is not required. +** 2. Altered source versions must be plainly marked as such, and must not be +** misrepresented as being the original software. +** 3. This notice may not be removed or altered from any source distribution. +** +** Notes: the C++ interfaces used require MSVC on win32, or at least the MSVC-compatible C++ ABI. Sorry, mingw users :( +** +*/ + +#ifndef _REAPER_PLUGIN_H_ +#define _REAPER_PLUGIN_H_ + + +#ifndef REASAMPLE_SIZE +#define REASAMPLE_SIZE 8 // if we change this it will break everything! +#endif + +#if REASAMPLE_SIZE == 4 +typedef float ReaSample; +#else +typedef double ReaSample; +#endif + + + +#ifdef _WIN32 +#include + +#define REAPER_PLUGIN_DLL_EXPORT __declspec(dllexport) +#define REAPER_PLUGIN_HINSTANCE HINSTANCE + +#else +//#include "../WDL/swell/swell.h" +#include "swell-types.h" +#include + +#define REAPER_PLUGIN_DLL_EXPORT __attribute__((visibility("default"))) +#define REAPER_PLUGIN_HINSTANCE void * +#endif + +#define REAPER_PLUGIN_ENTRYPOINT ReaperPluginEntry +#define REAPER_PLUGIN_ENTRYPOINT_NAME "ReaperPluginEntry" + +#ifdef _MSC_VER +#define INT64 __int64 +#define INT64_CONSTANT(x) (x##i64) +#else +#define INT64 long long +#define INT64_CONSTANT(x) (x##LL) +#endif + +#ifdef __GNUC__ + #define REAPER_STATICFUNC __attribute__((unused)) static +#else + #define REAPER_STATICFUNC static +#endif + +/* +** Endian-tools and defines (currently only __ppc__ and BIG_ENDIAN is recognized, for OS X -- all other platforms are assumed to be LE) +*/ + +REAPER_STATICFUNC int REAPER_BSWAPINT(int x) +{ + return ((((x))&0xff)<<24)|((((x))&0xff00)<<8)|((((x))&0xff0000)>>8)|(((x)>>24)&0xff); +} +REAPER_STATICFUNC void REAPER_BSWAPINTMEM(void *buf) +{ + char p[4],tmp; + memcpy(p,buf,4); + tmp=p[0]; p[0]=p[3]; p[3]=tmp; + tmp=p[1]; p[1]=p[2]; p[2]=tmp; + memcpy(buf,p,4); +} +REAPER_STATICFUNC void REAPER_BSWAPINTMEM8(void *buf) +{ + char p[8],tmp; + memcpy(p,buf,8); + tmp=p[0]; p[0]=p[7]; p[7]=tmp; + tmp=p[1]; p[1]=p[6]; p[6]=tmp; + tmp=p[2]; p[2]=p[5]; p[5]=tmp; + tmp=p[3]; p[3]=p[4]; p[4]=tmp; + memcpy(buf,p,8); +} + +#if defined(__ppc__) + +#define REAPER_BIG_ENDIAN +#define REAPER_FOURCC(d,c,b,a) (((unsigned int)(d)&0xff)|(((unsigned int)(c)&0xff)<<8)|(((unsigned int)(b)&0xff)<<16)|(((unsigned int)(a)&0xff)<<24)) + +#define REAPER_MAKEBEINT(x) (x) +#define REAPER_MAKEBEINTMEM(x) +#define REAPER_MAKEBEINTMEM8(x) +#define REAPER_MAKELEINT(x) REAPER_BSWAPINT(x) +#define REAPER_MAKELEINTMEM(x) REAPER_BSWAPINTMEM(x) +#define REAPER_MAKELEINTMEM8(x) REAPER_BSWAPINTMEM8(x) + +#else + +#define REAPER_FOURCC(a,b,c,d) (((unsigned int)(d)&0xff)|(((unsigned int)(c)&0xff)<<8)|(((unsigned int)(b)&0xff)<<16)|(((unsigned int)(a)&0xff)<<24)) + +#define REAPER_MAKELEINT(x) (x) +#define REAPER_MAKELEINTMEM(x) +#define REAPER_MAKELEINTMEM8(x) +#define REAPER_MAKEBEINT(x) REAPER_BSWAPINT(x) +#define REAPER_MAKEBEINTMEM(x) REAPER_BSWAPINTMEM(x) +#define REAPER_MAKEBEINTMEM8(x) REAPER_BSWAPINTMEM8(x) + +#endif + + +#if 1 +#define ADVANCE_TIME_BY_SAMPLES(t, spls, srate) ((t) += (spls)/(double)(srate)) // not completely accurate but still quite accurate. +#else +#define ADVANCE_TIME_BY_SAMPLES(t, spls, srate) ((t) = floor((t) * (srate) + spls + 0.5)/(double)(srate)) // good forever? may have other issues though if srate changes. disabled for now +#endif + + +// REAPER extensions must support this entry function: +// int ReaperPluginEntry(HINSTANCE hInstance, reaper_plugin_info_t *rec); +// return 1 if you are compatible (anything else will result in plugin being unloaded) +// if rec == NULL, then time to unload +// +// CLAP plugins can access the REAPER API via: +// const void *clap_host.get_extension(const clap_host *host, "cockos.reaper_extension"); +// which returns a pointer to a reaper_plugin_info_t struct +// +// CLAP plugins can also (v6.80+) get their context information by getFunc("clap_get_reaper_context") +// void *(*clap_get_reaper_context)(const clap_host *host, int sel); +// sel=1 for parent track if track FX +// sel=2 for parent take if item FX +// sel=3 for project +// sel=4 for FxDsp +// sel=5 for track channel count (INT_PTR) +// sel=6 for index in chain + +#define REAPER_PLUGIN_VERSION 0x20E + +typedef struct reaper_plugin_info_t +{ + int caller_version; // REAPER_PLUGIN_VERSION + + HWND hwnd_main; + + /* + Register() is the API that plug-ins register most things, be it keyboard shortcuts, project importers, etc. + Register() is also available by using GetFunc("plugin_register") + + extensions typically register things on load of the DLL, for example: + + static pcmsink_register_t myreg={ ... }; + rec->Register("pcmsink",&myreg); + + on plug-in unload (or if the extension wishes to remove it for some reason): + rec->Register("-pcmsink",&myreg); + + the "-" prefix is supported for most registration types. + some types support the < prefix to register at the start of the list + + Registration types: + + API_*: + if you have a function called myfunction(..) that you want to expose to other extensions, use: + rec->Register("API_myfunction",funcaddress); + other extensions then use GetFunc("myfunction") to get the function pointer. + + APIdef_*: + To make a function registered with API_* available via ReaScript, follow the API_ registration with: + double myfunction(char* str, int flag); + const char *defstring = "double\0char*,int\0str,flag\0help text for myfunction" + rec->Register("APIdef_myfunction",(void*)defstring); + defstring is four null-separated fields: return type, argument types, argument names, and help. + + APIvararg_*: + Used to set the reascript vararg function pointer for an API_: + (void *) (void * (*faddr_vararg)(void **arglist, int numparms)) + + numparms will typically be the full requested parameter count (including NULL pointers for any + unspecified optional parameters) + + arguments and return values: + integer as (void *)(INT_PTR) intval + double as (void *)&some_double_in_memory + pointers directly as pointers (can be NULL, especially if Optional) + + + hookcommand: + Registers a hook which runs prior to every action in the main section: + bool runCommand(int command, int flag); + rec->Register("hookcommand",runCommand); + runCommand() should return true if it processed the command (prevent further hooks or the action from running) + It is OK to call Main_OnCommand() from runCommand(), but it must check for and handle any recursion. + + hookpostcommand: + Registers a hook which runs after each action in the main section: + void postCommand(int command, int flag); + rec->Register("hookpostcommand",postCommand); + + hookcommand2: + Registers a hook which runs prior to every action triggered by a key/MIDI event: + bool onAction(KbdSectionInfo *sec, int command, int val, int val2, int relmode, HWND hwnd); + rec->Register("hookcommand2",hook); \ + onAction returns true if it processed the command (preventing further hooks or actions from running) + val/val2 are used for actions triggered by MIDI/OSC/mousewheel + - val = [0..127] and val2 = -1 for MIDI CC, + - val2 >=0 for MIDI pitch or OSC with value = (val2|(val<<7))/16383.0 + - relmode absolute(0) or 1/2/3 for relative adjust modes + + hookpostcommand2: + void (*hook)(KbdSectionInfo *section, int actionCommandID, int val, int valhw, int relmode, HWND hwnd, ReaProject *proj); + rec->Register("hookpostcommand2",hook); + + command_id: + Registers/looks up a command ID for an action. Parameter is a unique string with only A-Z, a-z, 0-9. + int command = Register("command_id","MyCommandName"); + returns 0 if unsupported/out of actions + + command_id_lookup: + Like command_id but only looks up, does not create a new command ID. + + pcmsink_ext: + Registers an extended audio sink type: + (pcmsink_register_ext_t *) + + pcmsink: + Registers an audio sink type: + (pcmsink_register_t *) + + pcmsrc: + Registers an audio source: + (pcmsrc_register_t *) + + timer: + Runs a timer periodically: + void (*timer_function)(); + + hwnd_info: (6.29+) + query information about a hwnd + int (*callback)(HWND hwnd, INT_PTR info_type); + -- note, for v7.23+ ( -- check with GetAppVersion() -- ), you may also use a function with this prototype: + int (*callback)(HWND hwnd, INT_PTR info_type, const MSG *msg); // if msg is non-NULL, it will have information about the currently-processing event. + + return 0 if hwnd is not a known window, or if info_type is unknown + + info_type: + 0 = query if hwnd should be treated as a text-field for purposes of global hotkeys + return: 1 if text field + return: -1 if not text field + 1 = query if global hotkeys should be processed for this context (6.60+) + return 1 if global hotkey should be skipped (and default accelerator processing used instead) + return -1 if global hotkey should be forced + + + file_in_project_ex: + void *p[2] = {(void *)fn, projptr }; + plugin_register("file_in_project_ex",p); + plugin_register("-file_in_project_ex",p); + + fn does not need to persist past the call of the function (it is copied) + projptr must be a valid ReaProject (the default NULL=g_project semantics do not apply). + file references are reference counted so you can add twice/remove twice etc. + + file_in_project_ex2: + Extended syntax to receive rename notifications, or to have your plug-in request that the file go in a subdirectory: + + INT_PTR fileInProjectCallback(void *_userdata, int msg, void *parm) { + if (msg == 0 && parm) + { + // rename notification, parm is (const char *)new filename + } + if (msg == 0x100) return (INT_PTR)"samples"; // subdirectory name, if desired (return 0 if not desired) + + if (msg == 0x101) return (INT_PTR)fxdspparentcontext_if_any; // if fx, optional + if (msg == 0x102) return (INT_PTR)takecontext_if_any; // if pcmsrc, optional + if (msg == 0x103) return (INT_PTR)"context/plug-in name"; // optional + return 0; + } + + void *p[4] = {(void *)fn, projptr, userdatacontext, fileInProjectCallback }; + plugin_register("file_in_project_ex2",p); + plugin_register("-file_in_project_ex2",p); + + toolbar_icon_map: + Allows a plugin to override default toolbar images for its registered commands. + + const char *GetToolbarIconName(const char *toolbar_name, int cmd, int state) + { + if (!strcmp(toolbarid,"Main toolbar") || !strncmp(toolbarid,"Floating toolbar",16)) + if (cmd == g_registered_command_id) return "toolbar_whatever"; + return NULL; + } + plugin_register("toolbar_icon_map", (void *)GetToolbarIconName); + + accel_section: + action_help: + custom_action: + gaccel: + hookcustommenu: + prefpage: + projectimport: + projectconfig: + editor: + accelerator: + csurf: + csurf_inst: + toggleaction: + on_update_hooks: + open_file_reduce: + + */ + + int (*Register)(const char *name, void *infostruct); // returns 1 if registered successfully + + // get a generic API function, there many of these defined. see reaper_plugin_functions.h + void * (*GetFunc)(const char *name); // returns 0 if function not found + +} reaper_plugin_info_t; + + + + +/**************************************************************************************** +**** interface for plugin objects to save/load state. they should use ../WDL/LineParser.h too... +***************************************************************************************/ + +// ProjectStateContext tempflags meaning for &0xFFFF +enum +{ + PROJSTATECTX_UNDO_REDO=1, + PROJSTATECTX_SAVE_LOAD=2, // project, track template, etc + PROJSTATECTX_CUTCOPY_PASTE=3, +}; +// &0xFFFF0000 is for receiver use + +#ifdef __cplusplus + +#ifndef _WDL_PROJECTSTATECONTEXT_DEFINED_ +#define _REAPER_PLUGIN_PROJECTSTATECONTEXT_DEFINED_ +class ProjectStateContext // this is also defined in WDL, need to keep these interfaces identical +{ +public: + virtual ~ProjectStateContext(){}; + +#ifdef __GNUC__ + virtual void __attribute__ ((format (printf,2,3))) AddLine(const char *fmt, ...) = 0; +#else + virtual void AddLine(const char *fmt, ...)=0; +#endif + virtual int GetLine(char *buf, int buflen)=0; // returns -1 on eof + + virtual INT64 GetOutputSize()=0; // output size written so far, only usable on REAPER 3.14+ + + virtual int GetTempFlag()=0; + virtual void SetTempFlag(int flag)=0; +}; +#endif + + + +/*************************************************************************************** +**** MIDI event definition and abstract list +***************************************************************************************/ + +struct MIDI_event_t +{ + int frame_offset; + int size; // bytes used by midi_message, can be >3, but should never be <3, even if a short 1 or 2 byte msg + unsigned char midi_message[4]; // size is number of bytes valid -- can be more than 4! + + // new helpers + bool is_note() const { return (midi_message[0]&0xe0)==0x80; } + bool is_note_on() const { + return (midi_message[0]&0xf0)==0x90 && midi_message[2]; + } + bool is_note_off() const { + switch (midi_message[0]&0xf0) + { + case 0x80: return true; + case 0x90: return midi_message[2]==0; + } + return false; + } + + enum { + CC_ALL_SOUND_OFF=120, + CC_ALL_NOTES_OFF=123, + CC_EOF_INDICATOR = CC_ALL_NOTES_OFF + }; +}; + +class MIDI_eventlist +{ +public: + virtual void AddItem(MIDI_event_t *evt)=0; + virtual MIDI_event_t *EnumItems(int *bpos)=0; + virtual void DeleteItem(int bpos)=0; + virtual int GetSize()=0; // size of block in bytes + virtual void Empty()=0; + +protected: + // this is only defined in REAPER 4.60+, for 4.591 and earlier you should delete only via the implementation pointer + virtual ~MIDI_eventlist() { } +}; + +#endif // __cplusplus + +typedef struct _MIDI_eventprops +{ + double ppqpos; + double ppqpos_end_or_bezier_tension; // only for note events or CC events + char flag; // &1=selected, &2=muted, >>4&0xF=cc shape + unsigned char msg[3]; // msg is not valid if varmsglen > 0 + char* varmsg; + int varmsglen; + int setflag; // &1:selected, &2:muted, &4:ppqpos, &8:endppqpos or bez tension, &16:msg1 high bits, &32:msg1 low bits, &64:msg2, &128:msg3, &256:varmsg, &1024:shape/tension fields used, &16384:no sort after set +} MIDI_eventprops; + + +/*************************************************************************************** +**** PCM source API +***************************************************************************************/ + + + +typedef struct _PCM_source_transfer_t +{ + double time_s; // start time of block + + double samplerate; // desired output samplerate and channels + int nch; + + int length; // desired length in sample(pair)s of output + + ReaSample *samples; // samples filled in (the caller allocates this) + int samples_out; // updated with number of sample(pair)s actually rendered + + MIDI_eventlist *midi_events; + + double approximate_playback_latency; // 0.0 if not supported + double absolute_time_s; + double force_bpm; +} PCM_source_transfer_t; + +#ifdef __cplusplus + +class REAPER_PeakGet_Interface; + +typedef struct _PCM_source_peaktransfer_t +{ + double start_time; // start time of block + double peakrate; // peaks per second (see samplerate below) + + int numpeak_points; // desired number of points for data + + int nchpeaks; // number of channels of peaks data requested + + ReaSample *peaks; // peaks output (caller allocated) + int peaks_out; // number of points actually output (less than desired means at end) + + enum + { + PEAKTRANSFER_PEAKS_MODE=0, + PEAKTRANSFER_WAVEFORM_MODE=1, + PEAKTRANSFER_MIDI_NOTE_MODE=2, + PEAKTRANSFER_MIDI_DRUM_MODE=3, + PEAKTRANSFER_MIDI_DRUM_TRIANGLE_MODE=4, + }; + int output_mode; // see enum above + + double absolute_time_s; + + ReaSample *peaks_minvals; // can be NULL, otherwise receives minimum values + int peaks_minvals_used; + + double samplerate; // peakrate is peaks per second, samplerate is used only as a hint for what style of peaks to draw, OK to pass in zero + +#define PEAKINFO_EXTRADATA_SPECTRAL1 ((int)'s') +#define PEAKINFO_EXTRADATA_SPECTROGRAM1 ((int)'g') +#define PEAKINFO_EXTRADATA_MIDITEXT ((int)'m') +#define PEAKINFO_EXTRADATA_LOUDNESS_DEPRECATED ((int)'l') // use PEAKINFO_EXTRADATA_LOUDNESS_RAW instead +#define PEAKINFO_EXTRADATA_LOUDNESS_RAW ((int)'r') +#define PEAKINFO_EXTRADATA_LOUDNESS_INTERNAL ((int)'!') + + int extra_requested_data_type; // PEAKINFO_EXTRADATA_* for spectral information + int extra_requested_data_out; // output: number of samples returned (== peaks_out if successful) + void *extra_requested_data; + + REAPER_PeakGet_Interface *__peakgetter; + + int extra_requested_data_type2; // PEAKINFO_EXTRADATA_* for spectral information + int extra_requested_data_out2; // output: number of samples returned (== peaks_out if successful) + void *extra_requested_data2; + +#ifdef __LP64__ + int *exp[25]; +#else + int *exp[23]; +#endif + + static inline int extra_blocksize(int extra_requested_data_type) + { + switch (extra_requested_data_type) + { + case PEAKINFO_EXTRADATA_SPECTRAL1: return SPECTRAL1_BYTES; + case PEAKINFO_EXTRADATA_SPECTROGRAM1: return SPECTROGRAM1_BLOCKSIZE_BYTES; + case PEAKINFO_EXTRADATA_MIDITEXT: return MIDITEXT_BYTES; + case PEAKINFO_EXTRADATA_LOUDNESS_DEPRECATED: return LOUDNESS_DEPRECATED_BYTES; + case PEAKINFO_EXTRADATA_LOUDNESS_RAW: return LOUDNESS_RAW_BYTES; + case PEAKINFO_EXTRADATA_LOUDNESS_INTERNAL: return LOUDNESS_INTERNAL_BYTES; + } + return 0; + } + + enum { + SPECTROGRAM1_BLOCKSIZE_BYTES=128 * 3 / 2, // 128 bins, 12 bits each (MSB1, (LSN1<<4)|LSN2, MSB2) + SPECTRAL1_BYTES=4, // one LE int per channel per sample spectral info: low 15 bits frequency, next 14 bits density (16383=tonal, 0=noise, 12288 = a bit noisy) + MIDITEXT_BYTES=1, // at most one character per pixel + LOUDNESS_DEPRECATED_BYTES=4, // 4 byte LE integer: LUFS-M low 12 bits, LUFS-S next 12 bits, 0-3000 valid: ex: 0 means -150.0 LU (consider this -inf), 1500 means +0 LU - loudness values returned for each channel even if the calculation is for all channels combined + LOUDNESS_RAW_BYTES=8, // 4 byte LE float LUFS-M low 32 bits, 4 byte LE float LUFS-S high 32 bits: + // stored values are a windowed, weighted mean square of samples for each channel, + // which must be combined to calculate total loudness. specifically, + // the stored values are z(i) in formula 2 in this document: + // https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.1770-0-200607-S!!PDF-E.pdf + LOUDNESS_INTERNAL_BYTES=4, // REAPER internal use only + }; + +} PCM_source_peaktransfer_t; + + +// used to update MIDI sources with new events during recording +typedef struct _REAPER_midi_realtime_write_struct_t +{ + double global_time; + double global_item_time; + double srate; + int length; // length in samples + int overwritemode; // 0=overdub, 1=replace, + // -1 = literal (do nothing just add), -2 = literal, do not apply default curves + // 65536+(16 bit mask) = replace notes on just these channels (touch-replace) + MIDI_eventlist *events; + double item_playrate; + + double latency; + + unsigned int *overwrite_actives; // [16(note)+16(CC)+16(poly AT)][4]; only used when overwritemode is >0 + // CC: 127=pitch, 126=program, 125=channel pressure + + double do_not_quantize_past_sec; // amount in future that quantizing should never move things past (or 0 for not used) +} midi_realtime_write_struct_t; + + +// abstract base class +class PCM_source +{ + public: + virtual ~PCM_source() { } + + virtual PCM_source *Duplicate()=0; + + virtual bool IsAvailable()=0; + virtual void SetAvailable(bool avail) { } // optional, if called with avail=false, close files/etc, and so on + virtual const char *GetType()=0; + virtual const char *GetFileName() { return NULL; } // return NULL if no filename (not purely a file) + virtual bool SetFileName(const char *newfn)=0; // return TRUE if supported, this will only be called when offline + + virtual PCM_source *GetSource() { return NULL; } + virtual void SetSource(PCM_source *src) { } + virtual int GetNumChannels()=0; // return number of channels + virtual double GetSampleRate()=0; // returns preferred sample rate. if < 1.0 then it is assumed to be silent (or MIDI) + virtual double GetLength()=0; // length in seconds + virtual double GetLengthBeats() { return -1.0; } // length in beats if supported + virtual int GetBitsPerSample() { return 0; } // returns bits/sample, if available. only used for metadata purposes, since everything returns as doubles anyway + virtual double GetPreferredPosition() { return -1.0; } // not supported returns -1 + + virtual int PropertiesWindow(HWND hwndParent)=0; + + virtual void GetSamples(PCM_source_transfer_t *block)=0; + virtual void GetPeakInfo(PCM_source_peaktransfer_t *block)=0; + + virtual void SaveState(ProjectStateContext *ctx)=0; + virtual int LoadState(const char *firstline, ProjectStateContext *ctx)=0; // -1 on error + + + // these are called by the peaks building UI to build peaks for files. + virtual void Peaks_Clear(bool deleteFile)=0; + virtual int PeaksBuild_Begin()=0; // returns nonzero if building is opened, otherwise it may mean building isn't necessary + virtual int PeaksBuild_Run()=0; // returns nonzero if building should continue + virtual void PeaksBuild_Finish()=0; // called when done + + virtual int Extended(int call, void *parm1, void *parm2, void *parm3) { return 0; } // return 0 if unsupported +}; + +typedef struct _REAPER_cue +{ + int m_id; // ignored for PCM_SINK_EXT_ADDCUE, populated for PCM_SOURCE_EXT_ENUMCUES + double m_time; + double m_endtime; + bool m_isregion; + char *m_name; // can be NULL if unnamed + int m_flags; // &1:DEPRECATED caller must call Extended(PCM_SOURCE_EXT_ENUMCUES, -1, &cue, 0) when finished, &2:time is QN, &0x10000:write cue regardless of sink settings, &4:is chapter, &(8|16)=8:has low confidence cue name, &(8|16)=16:has medium confidence cue name, &(8|16)=24:has high confidence cue name + char resvd[124]; // future expansion -- should be 0 +} REAPER_cue; + +typedef struct _REAPER_slice +{ + PCM_source* m_sliceSrc; + double m_beatSnapOffset; + int flag; // &1=only return beatsnapoffset, not slicesrc + char resvd[124]; // future expansion -- should be 0 +} REAPER_slice; + +typedef struct _REAPER_inline_positioninfo +{ + double draw_start_time; // project time at pixel start of draw + int draw_start_y; // if y-scroll is partway into the item, positive pixel value + double pixels_per_second; + + int width, height; // width and height of view of the item. if doing a partial update this may be larger than the bitmap passed in + int mouse_x, mouse_y; // valid only on mouse/key/setcursor/etc messages + + void *extraParms[8]; + // WM_KEYDOWN handlers can use MSG *msg = (MSG *)extraParms[0] + // WM_SETCURSOR handlers should set *extraParms[0] = hcursor +} REAPER_inline_positioninfo; + + +typedef struct _REAPER_tempochg +{ + double timepos, qnpos, bpm; + int tsnum, tsdenom; + int flag; // &1=linear tempo change, &2=internal use +} REAPER_tempochg; + + +#define PCM_SOURCE_EXT_INLINEEDITOR 0x100 /* parm1 = (void *)(INT_PTR)message, parm2/parm3 = parms + + note: for the WM_* constants, you can use windows.h if on Windows, and SWELL's definitions if on other platforms + note: for LICE_IBitmap interface, you can use LICE's definition + for SWELL and LICE, see Cockos WDL -- https://www.cockos.com/wdl + + NOTE: fx-embed documentation is now in reaper_plugin_fx_embed.h + + messages: + 0 = query if editor is available/supported. returns <0 if supported but unavailable, >0 if available, 0=if not supported + WM_CREATE to create the editor instance + WM_DESTROY to destroy the editor instance (nonzero if success) + + WM_LBUTTON*, WM_RBUTTON*, WM_MOUSEMOVE, WM_MOUSEWHEEL -- parm2=rsvd, parm3= REAPER_inline_positioninfo) + these can return any combination of: + REAPER_INLINE_RETNOTIFY_INVALIDATE + REAPER_INLINE_RETNOTIFY_SETCAPTURE + REAPER_INLINE_RETNOTIFY_SETFOCUS + + WM_KEYDOWN -- parm3=REAPER_inline_positioninfo*, MSG *kbmsg = (MSG *)rec->extraParms[0] + return nonzero to eat the key. can return REAPER_INLINE_RETNOTIFY_INVALIDATE. + + WM_SETCURSOR -- parm3=REAPER_inline_positioninfo* + -- *rec->extraParms[0] = hcursor + + paint messages: parm2 = LICE_IBitmap *, parm3 = (REAPER_inline_positioninfo*). + WM_ERASEBKGND -- draw first pass -- should return 1 if supported + WM_PAINT -- draw second pass -- should return 1 if paint supported + WM_NCPAINT -- draw third pass + + Notes on Retina/HiDPI: + on macOS retina, (int)LICE_IBitmap::Extended(LICE_EXT_GET_SCALING,NULL) may return 512. in this case LICE will internally render things double-sized + on other platforms HiDPI, if (int)LICE_IBitmap::Extended(LICE_EXT_GET_ADVISORY_SCALING,NULL) returns nonzero, then it is a 24.8 scale factor which things + should be scaled by. + + */ + +#define REAPER_INLINE_RETNOTIFY_INVALIDATE 0x1000000 // want refresh of display +#define REAPER_INLINE_RETNOTIFY_SETCAPTURE 0x2000000 // setcapture +#define REAPER_INLINE_RETNOTIFY_SETFOCUS 0x4000000 // set focus to item +#define REAPER_INLINE_RETNOTIFY_NOAUTOSCROLL 0x8000000 // modifier only valid when setcapture set + +#define REAPER_INLINEFLAG_SHOWALLTAKES 0x1000 // only valid as a return value flag for message=0, display in inactive take lanes +#define REAPER_INLINEFLAG_WANTOVERLAYEDCONTROLS 0x4000 // only valid as a return value flag for message=0, to have fades/etc still drawn over like normal + + + + + +#define PCM_SOURCE_EXT_PROJCHANGENOTIFY 0x2000 // parm1 = nonzero if activated project, zero if deactivated project + +#define PCM_SOURCE_EXT_OPENEDITOR 0x10001 // parm1=hwnd, implementation dependent parm2/parm3 +#define PCM_SOURCE_EXT_GETEDITORSTRING 0x10002 // parm1=index (0 or 1), parm2=(const char**)desc, optional parm3=(int*)has_had_editor +#define PCM_SOURCE_EXT_DEPRECATED_1 0x10003 // was PCM_SOURCE_EXT_CLOSESECONDARYSRC +#define PCM_SOURCE_EXT_SETITEMCONTEXT 0x10004 // parm1=MediaItem*, parm2=MediaItem_Take* +#define PCM_SOURCE_EXT_ADDMIDIEVENTS 0x10005 // parm1=pointer to midi_realtime_write_struct_t, nch=1 for replace, =0 for overdub, parm2=midi_quantize_mode_t* (optional) +#define PCM_SOURCE_EXT_GETASSOCIATED_RPP 0x10006 // parm1=pointer to char* that will receive a pointer to the string +#define PCM_SOURCE_EXT_GETMETADATA 0x10007 // parm1=pointer to name string, parm2=pointer to buffer, parm3=(int)buffersizemax. returns length used. defined strings are "TITLE", "ARTIST", "ALBUM", "TRACKNUMBER", "YEAR", "GENRE", "COMMENT", "DESC", "BPM", "KEY", "DB_CUSTOM" +#define PCM_SOURCE_EXT_SETASSECONDARYSOURCE 0x10008 // parm1=optional pointer to src (same subtype as receiver), if supplied, set the receiver as secondary src for parm1's editor, if not supplied, receiver has to figure out if there is an appropriate editor open to attach to, parm2/3 impl defined +#define PCM_SOURCE_EXT_SHOWMIDIPREVIEW 0x10009 // parm1=(MIDI_eventlist*), can be NULL for all-notes-off (also to check if this source supports showing preview at this moment) +#define PCM_SOURCE_EXT_SEND_EDITOR_MSG 0x1000A // impl defined parameters +#define PCM_SOURCE_EXT_SETSECONDARYSOURCELIST 0x1000B // parm1=(PCM_source**)sourcelist, parm2=list size, parm3=close any existing src not in the list +#define PCM_SOURCE_EXT_ISOPENEDITOR 0x1000C // returns 1 if this source is currently open in an editor, 2 if open in a secondary editor. parm1=1 to close. parm2=(int*)&flags to get extra flags (&1=editor is currently hidden) +#define PCM_SOURCE_EXT_SETEDITORGRID 0x1000D // parm1=(double*)griddiv: 0.25=quarter note, 1.0/3.0=half note triplet, etc. parm2=int* swingmode(1=swing), parm3=double*swingamt +#define PCM_SOURCE_EXT_GETITEMCONTEXT 0x10010 // parm1=MediaItem**, parm2=MediaItem_Take**, parm3=MediaTrack** +#define PCM_SOURCE_EXT_GETALLMETADATA_DEPRECATED 0x10011 // no longer supported +#define PCM_SOURCE_EXT_GETBITRATE 0x10012 // parm1=(double*)bitrate, if different from samplerate*channels*bitdepth/length +#define PCM_SOURCE_EXT_ENUMMETADATA 0x10013 // parm1=(int)index, parm2=(const char**)key, parm3=(const char**)value. enumerates all metadata, returns 0 when no more metadata exists +#define PCM_SOURCE_EXT_GETINFOSTRING 0x10014 // parm1=(char*)buffer, parm2=(int)buffer length, return the data that would be displayed in the properties window +#define PCM_SOURCE_EXT_CONFIGISFILENAME 0x20000 +#define PCM_SOURCE_EXT_WRITEMETADATA_DEPRECATED 0x20007 // no longer supported +#define PCM_SOURCE_EXT_WRITE_METADATA 0x20008 // parm1=char* new file name, parm2=(char**)NULL-terminated array of key,value,key2,value2,... pointers, parm3=flags (&1=merge, &2=do not allow update in-place). returns 1 if successfully generated a new file, 2 if original file was updated +#define PCM_SOURCE_EXT_GETBPMANDINFO 0x40000 // parm1=pointer to double for bpm. parm2=pointer to double for snap/downbeat offset (seconds). +#define PCM_SOURCE_EXT_GETNTRACKS 0x80000 // for midi data, returns number of tracks that would have been available. optional parm1=(int*)mask of channels available, mask&(1<<16)=metadata +#define PCM_SOURCE_EXT_GETTITLE 0x80001 // parm1=(char**)title (string persists in plugin) +#define PCM_SOURCE_EXT_ENUMTEMPOMAP 0x80002 // parm1=index, parm2=pointer to REAPER_tempochg, returns 0 if no tempo map or enumeration complete +#define PCM_SOURCE_EXT_WANTOLDBEATSTYLE 0x80003 +#define PCM_SOURCE_EXT_GETNOTATIONSETTINGS 0x80004 // parm1=(int)what, (what==0) => parm2=(double*)keysigmap, parm3=(int*)keysigmapsize; (what==1) => parm2=(int*)display transpose semitones, (what==2) => parm2=(char*)clef1, parm3=(char*)clef2 +#define PCM_SOURCE_EXT_RELOADTRACKDATA 0x80005 // internal use +#define PCM_SOURCE_EXT_GIVE_TRACK_HINT 0x8000A // alias of PCM_SINK_EXT_GIVE_TRACK_HINT +#define PCM_SOURCE_EXT_WANT_TRIM 0x90001 // parm1=(int64*)total number of decoded samples after trimming, parm2=(int*)number of samples to trim from start, parm3=(int*)number of samples to trim from end +#define PCM_SOURCE_EXT_WANTTRIM_DEPRECATED 0x90002 // no longer supported +#define PCM_SOURCE_EXT_TRIMITEM 0x90003 // parm1=lrflag, parm2=double *{position,length,startoffs,rate} +#define PCM_SOURCE_EXT_EXPORTTOFILE 0x90004 // parm1=output filename, only currently supported by MIDI but in theory any source could support this +#define PCM_SOURCE_EXT_ENUMCUES 0x90005 // DEPRECATED, use PCM_SOURCE_EXT_ENUMCUES_EX instead. parm1=(int) index of cue to get (-1 to free cue), parm2=(optional)REAPER_cue **. Returns 0 and sets parm2 to NULL when out of cues. return value otherwise is how much to advance parm2 (1, or 2 usually) +#define PCM_SOURCE_EXT_ENUMCUES_EX 0x90016 // parm1=(int) index of cue (source must provide persistent backing store for cue->m_name), parm2=(REAPER_cue*) optional. Returns 0 when out of cues, otherwise returns how much to advance index (1 or 2 usually). +// a PCM_source may be the parent of a number of beat-based slices, if so the parent should report length and nchannels only, handle ENUMSLICES, and be deleted after the slices are retrieved +#define PCM_SOURCE_EXT_ENUMSLICES 0x90006 // parm1=(int*) index of slice to get, parm2=REAPER_slice* (pointing to caller's existing slice struct), parm3=(double*)bpm. if parm2 passed in zero, returns the number of slices. returns 0 if no slices or out of slices. +#define PCM_SOURCE_EXT_ENDPLAYNOTIFY 0x90007 // notify a source that it can release any pooled resources +#define PCM_SOURCE_EXT_SETPREVIEWTEMPO 0x90008 // parm1=(double*)bpm, only meaningful for MIDI or slice-based source media; bpm==0 to follow project tempo changes + +enum { RAWMIDI_NOTESONLY=1, RAWMIDI_UNFILTERED=2, RAWMIDI_CHANNELFILTER=3 }; // if RAWMIDI_CHANNELFILTER, flags>>4 is a mask of which channels to play +#define PCM_SOURCE_EXT_GETRAWMIDIEVENTS 0x90009 // parm1 = (PCM_source_transfer_t *), parm2 = RAWMIDI flags + +#define PCM_SOURCE_EXT_SETRESAMPLEMODE 0x9000A // parm1= mode to pass to resampler->Extended(RESAMPLE_EXT_SETRSMODE,mode,0,0) +#define PCM_SOURCE_EXT_NOTIFYPREVIEWPLAYPOS 0x9000B // parm1 = ptr to double of play position, or NULL if stopped +#define PCM_SOURCE_EXT_SETSIZE 0x9000C // parm1=(double*)startpos, parm2=(double*)endpos, parm3=flags. Start can be negative. Receiver may adjust start/end to avoid erasing content, in which case the adjusted values are returned in parm1 and parm2. parm3/flags: 1 if start/end in QN (always the case now). 2=resize even pooled items +#define PCM_SOURCE_EXT_GETSOURCETEMPO 0x9000D // parm1=(double*)bpm, parm2=(int*)timesig_numerator<<8|timesig_denominator, parm3=(double*)current preview tempo if applicable. this is for reporting purposes only, does not necessarily mean the media should be adjusted (as PCM_SOURCE_EXT_GETBPMANDINFO means) +#define PCM_SOURCE_EXT_ISABNORMALAUDIO 0x9000E // return 1 if rex, video, etc (meaning file export will just copy file directly rather than trim/converting) +#define PCM_SOURCE_EXT_GETPOOLEDMIDIID 0x9000F // parm1=(char*)id, parm2=(int*)pool user count, parm3=(MediaItem_Take**)firstuser +#define PCM_SOURCE_EXT_REMOVEFROMMIDIPOOL 0x90010 +#define PCM_SOURCE_EXT_GETHASH 0x90011 // parm1=(WDL_UINT64*)hash (64-bit hash of the source data) +#define PCM_SOURCE_EXT_GETIMAGE 0x90012 // parm1=(LICE_IBitmap**)image. parm2 = NULL or pointer to int, which is (w<<16)|h desired approx +#define PCM_SOURCE_EXT_NOAUDIO 0x90013 // return 1 if video file with no audio. if parm1 is non-NULL, will (int*)parm1=1 if a video file with audio and no video +#define PCM_SOURCE_EXT_HASMIDI 0x90014 // returns 1 if contains any MIDI data, parm1=(double*)time offset of first event +#define PCM_SOURCE_EXT_DELETEMIDINOTES 0x90015 // parm1=(double*)minlen (0.125 for 1/8 notes, etc), parm2=1 if only trailing small notes should be deleted, parm3=(bool*)true if any notes were deleted (return) +#define PCM_SOURCE_EXT_GETGUID 0x90017 // parm1=(GUID*)guid +#define PCM_SOURCE_EXT_DOPASTEINITEM 0x90100 // no parms used, acts as a paste from clipboard +#define PCM_SOURCE_EXT_GETNOTERANGE 0x90018 // parm1=(int*)low note, parm2=(int*)high note +#define PCM_SOURCE_EXT_PPQCONVERT 0x90020 // parm1=(double*)pos, parm2=(int)flag 0=ppq to proj time, 1=proj time to ppq +#define PCM_SOURCE_EXT_COUNTMIDIEVTS 0x90021 // parm1=(int*)notecnt, parm2=(int*)ccevtcnt, parm3=(int*)metaevtcnt +#define PCM_SOURCE_EXT_GETSETMIDIEVT 0x90022 // parm1=(MIDI_eventprops*)event properties (NULL to delete); parm2=(int)event index (<0 to insert); parm2=(int)flag: 1=index counts notes only, 2=index counts CC only, 3=index counts meta-events only +#define PCM_SOURCE_EXT_GETSUGGESTEDTEXT 0x90023 // parm1=char ** which will receive pointer to suggested label text, if any +#define PCM_SOURCE_EXT_GETSCALE 0x90024 // parm1=unsigned int: &0xF=pitch (0=C), &0x10=root, &0x20=min2, &0x40=maj2, &0x80=min3, &0xF0=maj3, &0x100=4, etc) ; parm2=(char*)name (optional), parm3=int size of name buffer +#define PCM_SOURCE_EXT_SELECTCONTENT 0x90025 // parm1=1 to select, 0 to deselect +#define PCM_SOURCE_EXT_GETGRIDINFO 0x90026 // parm1=(double*)snap grid size, parm2=(double*)swing strength, parm3=(double*)note insert length, -1 if follows grid size +#define PCM_SOURCE_EXT_SORTMIDIEVTS 0x9027 +#define PCM_SOURCE_EXT_MIDI_COMPACTPHRASES 0x90028 // compact the notation phrase ID space +#define PCM_SOURCE_EXT_GETSETALLMIDI 0x90029 // parm1=(unsigned char*)data buffer, parm2=(int*)buffer length in bytes, parm2=(1:set, 0:get). Buffer is a list of { int offset, char flag, int msglen, unsigned char msg[] }. offset: MIDI ticks from previous event, flag: &1=selected &2=muted, msglen: byte length of msg (usually 3), msg: the MIDI message. +#define PCM_SOURCE_EXT_DISABLESORTMIDIEVTS 0x90030 // disable sorting for PCM_SOURCE_EXT_GETSETMIDIEVT until PCM_SOURCE_EXT_SORTMIDIEVTS is called +#define PCM_SOURCE_EXT_GETPOOLEDMIDIID2 0x90031 // parm1=(GUID*)id, parm2=(int*)pool user count, parm3=(MediaItem_Take**)firstuser +#define PCM_SOURCE_EXT_GETSETMIDICHANFILTER 0x90032 // parm1=(int*)filter: filter&(1<RPP converter, allowing you to generate directly to a ProjectStateContext +*/ +typedef struct _REAPER_project_import_register_t // register with "projectimport" +{ + bool (*WantProjectFile)(const char *fn); // is this our file? + const char *(*EnumFileExtensions)(int i, char **descptr); // call increasing i until returns NULL. if descptr's output is NULL, use last description + int (*LoadProject)(const char *fn, ProjectStateContext *genstate); // return 0=ok, Generate RPP compatible project info in genstate +} project_import_register_t; + + +typedef struct project_config_extension_t // register with "projectconfig" +{ + // plug-ins may or may not want to save their undo states (look at isUndo) + // undo states will be saved if UNDO_STATE_MISCCFG is set (for adding your own undo points) + bool (*ProcessExtensionLine)(const char *line, ProjectStateContext *ctx, bool isUndo, struct project_config_extension_t *reg); // returns BOOL if line (and optionally subsequent lines) processed (return false if not plug-ins line) + void (*SaveExtensionConfig)(ProjectStateContext *ctx, bool isUndo, struct project_config_extension_t *reg); + + // optional: called on project load/undo before any (possible) ProcessExtensionLine. NULL is OK too + // also called on "new project" (wont be followed by ProcessExtensionLine calls in that case) + void (*BeginLoadProjectState)(bool isUndo, struct project_config_extension_t *reg); + + void *userData; +} project_config_extension_t; + + +typedef struct prefs_page_register_t // register useing "prefpage" +{ + const char *idstr; // simple id str + const char *displayname; + HWND (*create)(HWND par); + int par_id; + const char *par_idstr; + + int childrenFlag; // 1 for will have children + + void *treeitem; + HWND hwndCache; + + char _extra[64]; // + +} prefs_page_register_t; + +typedef struct audio_hook_register_t +{ + void (*OnAudioBuffer)(bool isPost, int len, double srate, struct audio_hook_register_t *reg); // called twice per frame, isPost being false then true + void *userdata1; + void *userdata2; + + // plug-in should zero these and they will be set by host + // only call from OnAudioBuffer, nowhere else!!! + int input_nch, output_nch; + ReaSample *(*GetBuffer)(bool isOutput, int idx); + +} audio_hook_register_t; + +/* +** Allows you to get callback from the audio thread before and after REAPER's processing. +** register with Audio_RegHardwareHook() + + Note that you should be careful with this! :) + +*/ + + +/* +** Customizable keyboard section definition etc +** +** Plug-ins may register keyboard action sections in by registering a "accel_section" to a KbdSectionInfo*. +*/ + +struct KbdAccel; + +typedef struct _REAPER_KbdCmd +{ + DWORD cmd; // action command ID + const char *text; // description of action +} KbdCmd; + +typedef struct _REAPER_KbdKeyBindingInfo +{ + int key; // key identifier + int cmd; // action command ID + int flags; // key flags +} KbdKeyBindingInfo; + + + +typedef struct _REAPER_KbdSectionInfo +{ + int uniqueID; // 0=main, < 0x10000000 for cockos use only plzkthx + const char *name; // section name + + KbdCmd *action_list; // list of assignable actions + int action_list_cnt; + + const KbdKeyBindingInfo *def_keys; // list of default key bindings + int def_keys_cnt; + + // hwnd is 0 if MIDI etc. return false if ignoring + bool (*onAction)(int cmd, int val, int valhw, int relmode, HWND hwnd); + + // this is allocated by the host not by the plug-in using it + // the user can edit the list of actions/macros +#ifdef _WDL_PTRLIST_H_ + WDL_PtrList *accels; + WDL_TypedBuf* recent_cmds; +#else + void* accels; + void *recent_cmds; +#endif + + void *extended_data[32]; // for internal use +} KbdSectionInfo; + + + +typedef struct _REAPER_preview_register_t +{ +/* +** Note: you must initialize/deinitialize the cs/mutex (depending on OS) manually, and use it if accessing most parameters while the preview is active. +*/ + +#ifdef _WIN32 + CRITICAL_SECTION cs; +#else + pthread_mutex_t mutex; +#endif + PCM_source *src; + int m_out_chan; // &1024 means mono, low 10 bits are index of first channel + double curpos; + bool loop; + double volume; + + double peakvol[2]; + void *preview_track; // used for track previews, but only if m_out_chan == -1 +} preview_register_t; + +/* +** preview_register_t is not used with the normal register system, instead it's used with PlayPreview(), StopPreview(), PlayTrackPreview(), StopTrackPreview() +*/ + + + +#ifdef REAPER_WANT_DEPRECATED_COLORTHEMESTUFF /* no longer used -- see icontheme.h and GetColorThemeStruct() */ + +/* +** ColorTheme API access, these are used with GetColorTheme() +*/ + +#define COLORTHEMEIDX_TIMELINEFG 0 +#define COLORTHEMEIDX_ITEMTEXT 1 +#define COLORTHEMEIDX_ITEMBG 2 +#define COLORTHEMEIDX_TIMELINEBG 4 +#define COLORTHEMEIDX_TIMELINESELBG 5 +#define COLORTHEMEIDX_ITEMCONTROLS 6 +#define COLORTHEMEIDX_TRACKBG1 24 +#define COLORTHEMEIDX_TRACKBG2 25 +#define COLORTHEMEIDX_PEAKS1 28 +#define COLORTHEMEIDX_PEAKS2 29 +#define COLORTHEMEIDX_EDITCURSOR 35 +#define COLORTHEMEIDX_GRID1 36 +#define COLORTHEMEIDX_GRID2 37 +#define COLORTHEMEIDX_MARKER 38 +#define COLORTHEMEIDX_REGION 40 +#define COLORTHEMEIDX_GRID3 61 +#define COLORTHEMEIDX_LOOPSELBG 100 + +#define COLORTHEMEIDX_ITEM_LOGFONT -2 // these return LOGFONT * as (int) +#define COLORTHEMEIDX_TL_LOGFONT -1 + + +#define COLORTHEMEIDX_MIDI_TIMELINEBG 66 +#define COLORTHEMEIDX_MIDI_TIMELINEFG 67 +#define COLORTHEMEIDX_MIDI_GRID1 68 +#define COLORTHEMEIDX_MIDI_GRID2 69 +#define COLORTHEMEIDX_MIDI_GRID3 70 +#define COLORTHEMEIDX_MIDI_TRACKBG1 71 +#define COLORTHEMEIDX_MIDI_TRACKBG2 72 +#define COLORTHEMEIDX_MIDI_ENDPT 73 +#define COLORTHEMEIDX_MIDI_NOTEBG 74 +#define COLORTHEMEIDX_MIDI_NOTEFG 75 +#define COLORTHEMEIDX_MIDI_ITEMCONTROLS 76 +#define COLORTHEMEIDX_MIDI_EDITCURSOR 77 +#define COLORTHEMEIDX_MIDI_PKEY1 78 +#define COLORTHEMEIDX_MIDI_PKEY2 79 +#define COLORTHEMEIDX_MIDI_PKEY3 80 +#define COLORTHEMEIDX_MIDI_PKEYTEXT 81 +#define COLORTHEMEIDX_MIDI_OFFSCREENNOTE 103 +#define COLORTHEMEIDX_MIDI_OFFSCREENNOTESEL 104 + +#endif // colortheme stuff deprecated + +/* +** Screenset API +** +*/ + +/* + Note that "id" is a unique identifying string (usually a GUID etc) that is valid across + program runs (stored in project etc). lParam is instance-specific parameter (i.e. "this" pointer etc). +*/ +enum +{ + SCREENSET_ACTION_GETHWND = 0, // returns HWND of screenset window. If searching for a specific window, it will be passed in actionParm + + SCREENSET_ACTION_IS_DOCKED = 1, // returns 1 if docked + SCREENSET_ACTION_SWITCH_DOCK = 4, //dock if undocked and vice-versa + + SCREENSET_ACTION_LOAD_STATE=0x100, // load state from actionParm (of actionParmSize). if both are NULL, hide. + SCREENSET_ACTION_SAVE_STATE, // save state to actionParm, max length actionParmSize (will usually be max(4096, value_returned by SCREENSET_ACTION_WANT_STATE_SIZE)), return length actually used + SCREENSET_ACTION_WANT_STATE_SIZE, // returns desired size for SCREENSET_ACTION_SAVE_STATE, may or may not be fulfilled! +}; +typedef LRESULT (*screensetNewCallbackFunc)(int action, const char *id, void *param, void *actionParm, int actionParmSize); + +// This is managed using screenset_registerNew(), screenset_unregister(), etc + + +/* +** MIDI hardware device access. +** +*/ + +#ifdef __cplusplus + +class midi_Output +{ +public: + virtual ~midi_Output() {} + + virtual void BeginBlock() { } // outputs can implement these if they wish to have timed block sends + virtual void EndBlock(int length, double srate, double curtempo) { } + virtual void SendMsg(MIDI_event_t *msg, int frame_offset)=0; // frame_offset can be <0 for "instant" if supported + virtual void Send(unsigned char status, unsigned char d1, unsigned char d2, int frame_offset)=0; // frame_offset can be <0 for "instant" if supported + + virtual void Destroy() { delete this; } // allows implementations to do asynchronous destroy (5.95+) + +}; + + +class midi_Input +{ +public: + virtual ~midi_Input() {} + + virtual void start()=0; + virtual void stop()=0; + + virtual void SwapBufs(unsigned int timestamp)=0; // DEPRECATED call SwapBufsPrecise() instead // timestamp=process ms + + virtual void RunPreNoteTracking(int isAccum) { } + + virtual MIDI_eventlist *GetReadBuf()=0; // note: the event list here has frame offsets that are in units of 1/1024000 of a second, NOT sample frames + + virtual void SwapBufsPrecise(unsigned int coarsetimestamp, double precisetimestamp) // coarse=process ms, precise=process sec, the target will know internally which to use + { + SwapBufs(coarsetimestamp); // default impl is for backward compatibility + } + + virtual void Destroy() { delete this; } // allows implementations to do asynchronous destroy (5.95+) +}; + + + +/* +** Control Surface API +*/ + +class ReaProject; +class MediaTrack; +class MediaItem; +class MediaItem_Take; +class TrackEnvelope; + +class IReaperControlSurface +{ + public: + IReaperControlSurface() { } + virtual ~IReaperControlSurface() { } + + virtual const char *GetTypeString()=0; // simple unique string with only A-Z, 0-9, no spaces or other chars + virtual const char *GetDescString()=0; // human readable description (can include instance specific info) + virtual const char *GetConfigString()=0; // string of configuration data + + virtual void CloseNoReset() { } // close without sending "reset" messages, prevent "reset" being sent on destructor + + + virtual void Run() { } // called 30x/sec or so. + + + // these will be called by the host when states change etc + virtual void SetTrackListChange() { } + virtual void SetSurfaceVolume(MediaTrack *trackid, double volume) { } + virtual void SetSurfacePan(MediaTrack *trackid, double pan) { } + virtual void SetSurfaceMute(MediaTrack *trackid, bool mute) { } + virtual void SetSurfaceSelected(MediaTrack *trackid, bool selected) { } + virtual void SetSurfaceSolo(MediaTrack *trackid, bool solo) { } // trackid==master means "any solo" + virtual void SetSurfaceRecArm(MediaTrack *trackid, bool recarm) { } + virtual void SetPlayState(bool play, bool pause, bool rec) { } + virtual void SetRepeatState(bool rep) { } + virtual void SetTrackTitle(MediaTrack *trackid, const char *title) { } + virtual bool GetTouchState(MediaTrack *trackid, int isPan) { return false; } + virtual void SetAutoMode(int mode) { } // automation mode for current track + + virtual void ResetCachedVolPanStates() { } // good to flush your control states here + + virtual void OnTrackSelection(MediaTrack *trackid) { } // track was selected + + virtual bool IsKeyDown(int key) { return false; } // VK_CONTROL, VK_MENU, VK_SHIFT, etc, whatever makes sense for your surface + + virtual int Extended(int call, void *parm1, void *parm2, void *parm3) { return 0; } // return 0 if unsupported +}; + +#define CSURF_EXT_RESET 0x0001FFFF // clear all surface state and reset (harder reset than SetTrackListChange) +#define CSURF_EXT_SETINPUTMONITOR 0x00010001 // parm1=(MediaTrack*)track, parm2=(int*)recmonitor +#define CSURF_EXT_SETMETRONOME 0x00010002 // parm1=0 to disable metronome, !0 to enable +#define CSURF_EXT_SETAUTORECARM 0x00010003 // parm1=0 to disable autorecarm, !0 to enable +#define CSURF_EXT_SETRECMODE 0x00010004 // parm1=(int*)record mode: 0=autosplit and create takes, 1=replace (tape) mode +#define CSURF_EXT_SETSENDVOLUME 0x00010005 // parm1=(MediaTrack*)track, parm2=(int*)sendidx, parm3=(double*)volume +#define CSURF_EXT_SETSENDPAN 0x00010006 // parm1=(MediaTrack*)track, parm2=(int*)sendidx, parm3=(double*)pan +#define CSURF_EXT_SETFXENABLED 0x00010007 // parm1=(MediaTrack*)track, parm2=(int*)fxidx, parm3=0 if bypassed, !0 if enabled +#define CSURF_EXT_SETFXPARAM 0x00010008 // parm1=(MediaTrack*)track, parm2=(int*)(fxidx<<16|paramidx), parm3=(double*)normalized value +#define CSURF_EXT_SETFXPARAM_RECFX 0x00010018 // parm1=(MediaTrack*)track, parm2=(int*)(fxidx<<16|paramidx), parm3=(double*)normalized value +#define CSURF_EXT_SETBPMANDPLAYRATE 0x00010009 // parm1=*(double*)bpm (may be NULL), parm2=*(double*)playrate (may be NULL) +#define CSURF_EXT_SETLASTTOUCHEDFX 0x0001000A // parm1=(MediaTrack*)track, parm2=(int*)mediaitemidx (may be NULL), parm3=(int*)fxidx. all parms NULL=clear last touched FX +#define CSURF_EXT_SETFOCUSEDFX 0x0001000B // parm1=(MediaTrack*)track, parm2=(int*)mediaitemidx (may be NULL), parm3=(int*)fxidx. all parms NULL=clear focused FX +#define CSURF_EXT_SETLASTTOUCHEDTRACK 0x0001000C // parm1=(MediaTrack*)track +#define CSURF_EXT_SETMIXERSCROLL 0x0001000D // parm1=(MediaTrack*)track, leftmost track visible in the mixer +#define CSURF_EXT_SETPAN_EX 0x0001000E // parm1=(MediaTrack*)track, parm2=(double*)pan, parm3=(int*)mode 0=v1-3 balance, 3=v4+ balance, 5=stereo pan, 6=dual pan. for modes 5 and 6, (double*)pan points to an array of two doubles. if a csurf supports CSURF_EXT_SETPAN_EX, it should ignore CSurf_SetSurfacePan. +#define CSURF_EXT_SETRECVVOLUME 0x00010010 // parm1=(MediaTrack*)track, parm2=(int*)recvidx, parm3=(double*)volume +#define CSURF_EXT_SETRECVPAN 0x00010011 // parm1=(MediaTrack*)track, parm2=(int*)recvidx, parm3=(double*)pan +#define CSURF_EXT_SETFXOPEN 0x00010012 // parm1=(MediaTrack*)track, parm2=(int*)fxidx, parm3=0 if UI closed, !0 if open +#define CSURF_EXT_SETFXCHANGE 0x00010013 // parm1=(MediaTrack*)track, whenever FX are added, deleted, or change order. flags=(INT_PTR)parm2, &1=rec fx +#define CSURF_EXT_SETPROJECTMARKERCHANGE 0x00010014 // whenever project markers are changed +#define CSURF_EXT_TRACKFX_PRESET_CHANGED 0x00010015 // parm1=(MediaTrack*)track, parm2=(int*)fxidx (6.13+ probably) +#define CSURF_EXT_SUPPORTS_EXTENDED_TOUCH 0x00080001 // returns nonzero if GetTouchState can take isPan=2 for width, etc +#define CSURF_EXT_MIDI_DEVICE_REMAP 0x00010099 // parm1 = isout, parm2 = old idx, parm3 = new idx + +typedef struct _REAPER_reaper_csurf_reg_t +{ + const char *type_string; // simple unique string with only A-Z, 0-9, no spaces or other chars + const char *desc_string; // human readable description + + IReaperControlSurface *(*create)(const char *type_string, const char *configString, int *errStats); // errstats gets |1 if input error, |2 if output error + HWND (*ShowConfig)(const char *type_string, HWND parent, const char *initConfigString); +} reaper_csurf_reg_t; // register using "csurf"/"-csurf" + +// note you can also add a control surface behind the scenes with "csurf_inst" (IReaperControlSurface*)instance + +#endif // __cplusplus + + +#ifndef UNDO_STATE_ALL +#define UNDO_STATE_ALL 0xFFFFFFFF +#define UNDO_STATE_TRACKCFG 1 // has track/master vol/pan/routing, routing/hwout envelopes too +#define UNDO_STATE_FX 2 // track/master fx +#define UNDO_STATE_ITEMS 4 // track items and linkedlanes +#define UNDO_STATE_MISCCFG 8 // loop selection, markers, regions, extensions! +#define UNDO_STATE_FREEZE 16 // freeze state -- note that isfreeze is used independently, this is only used for the undo system to serialize the already frozen state +#define UNDO_STATE_TRACKENV 32 // non-FX envelopes only +#define UNDO_STATE_FXENV 64 // FX envelopes, implied by UNDO_STATE_FX too +#define UNDO_STATE_POOLEDENVS 128 // contents of pooled envs -- not position, length, rate etc of pooled env instances, which is part of envelope state +#endif + +#ifndef IS_MSG_VIRTKEY + #ifdef _WIN32 + #define IS_MSG_VIRTKEY(msg) ((msg)->message != WM_CHAR) + #else + #define IS_MSG_VIRTKEY(msg) ((msg)->lParam&FVIRTKEY) + #endif +#endif +#define IS_MSG_FKEY(msg) ((msg)->wParam >= VK_F1 && (msg)->wParam <= VK_F24 && IS_MSG_VIRTKEY(msg)) + +#define WDL_FILEWRITE_ON_ERROR(is_full) update_disk_counters(0,-101010110 - ((is_full) ? 1 : 0)); + +#define REAPER_MAX_CHANNELS 128 + +#endif//_REAPER_PLUGIN_H_ diff --git a/External/reaper-plugins/reaper_vst3_interfaces.h b/External/reaper-plugins/reaper_vst3_interfaces.h new file mode 100644 index 0000000..43cc6d0 --- /dev/null +++ b/External/reaper-plugins/reaper_vst3_interfaces.h @@ -0,0 +1,31 @@ +#ifndef _REAPER_VST3_INTERFACES_H_ +#define _REAPER_VST3_INTERFACES_H_ + +class IReaperHostApplication : public FUnknown // available from IHostApplication in REAPER v5.02+ +{ +public: + // Gets a REAPER Extension API function by name, returns NULL is failed + virtual void* PLUGIN_API getReaperApi(CStringA funcname) = 0; + + virtual void* PLUGIN_API getReaperParent(uint32 w) = 0; // get parent track(=1), take(=2), project(=3), fxdsp(=4), trackchan(=5) + + // Multi-purpose function, returns NULL if unsupported + virtual void* PLUGIN_API reaperExtended(uint32 call, void *parm1, void *parm2, void *parm3) = 0; + + static const FUID iid; +}; + +DECLARE_CLASS_IID (IReaperHostApplication, 0x79655E36, 0x77EE4267, 0xA573FEF7, 0x4912C27C) + +class IReaperUIEmbedInterface : public FUnknown // supported by REAPER v6.24+, queried from plug-in IEditController +{ + public: + // note: VST2 uses CanDo "hasCockosEmbeddedUI"==0xbeef0000, then opcode=effVendorSpecific, index=effEditDraw, opt=(float)msg, value=parm2, ptr=parm3 + // see reaper_plugin_fx_embed.h + virtual Steinberg::TPtrInt embed_message(int msg, Steinberg::TPtrInt parm2, Steinberg::TPtrInt parm3) = 0; + + static const FUID iid; +}; + +DECLARE_CLASS_IID (IReaperUIEmbedInterface, 0x049bf9e7, 0xbc74ead0, 0xc4101e86, 0x7f725981) +#endif \ No newline at end of file diff --git a/External/reaper-plugins/swell-types.h b/External/reaper-plugins/swell-types.h new file mode 100644 index 0000000..b33eb67 --- /dev/null +++ b/External/reaper-plugins/swell-types.h @@ -0,0 +1,1470 @@ +/* Cockos SWELL (Simple/Small Win32 Emulation Layer for Linux/OSX) + Copyright (C) 2006 and later, Cockos, Inc. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + + SWELL provides _EXTREMELY BASIC_ win32 wrapping for OS X and maybe other platforms. + + */ + +#ifndef _WDL_SWELL_H_TYPES_DEFINED_ +#define _WDL_SWELL_H_TYPES_DEFINED_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__cplusplus) +#include +#endif + +#include +typedef intptr_t INT_PTR, *PINT_PTR, LONG_PTR, *PLONG_PTR; +typedef uintptr_t UINT_PTR, *PUINT_PTR, ULONG_PTR, *PULONG_PTR, DWORD_PTR, *PDWORD_PTR; + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef S_OK +#define S_OK 0 +#endif +#ifndef E_FAIL +#define E_FAIL (-1) +#endif + + +#ifdef SWELL_USE_WIN32_RGB + +// define SWELL_USE_WIN32_RGB project-wide if you want the RGB byte ordering +// of RGB(), GetRValue(), etc, to match win32 + +#define RGB(r,g,b) ((((BYTE)(b))<<16)|(((BYTE)(g))<<8)|((BYTE)(r))) +#define GetBValue(x) (((x)>>16)&0xff) +#define GetGValue(x) (((x)>>8)&0xff) +#define GetRValue(x) ((x)&0xff) + +#else + +// the byte ordering of RGB() etc is different than on win32 +#define RGB(r,g,b) ((((BYTE)(r))<<16)|(((BYTE)(g))<<8)|((BYTE)(b))) +#define GetRValue(x) (((x)>>16)&0xff) +#define GetGValue(x) (((x)>>8)&0xff) +#define GetBValue(x) ((x)&0xff) + +#define SWELL_BROKEN_RGB_ORDER + +#endif + +// basic platform compat defines +#ifndef stricmp +#define stricmp(x,y) strcasecmp(x,y) +#endif +#ifndef strnicmp +#define strnicmp(x,y,z) strncasecmp(x,y,z) +#endif + +#define DeleteFile(x) (!unlink(x)) +#define MoveFile(x,y) (!rename(x,y)) +#define GetCurrentDirectory(sz,buf) (!getcwd(buf,sz)) +#define SetCurrentDirectory(buf) (!chdir(buf)) +#define CreateDirectory(x,y) (!mkdir((x),0755)) + +#ifndef wsprintf +#define wsprintf sprintf +#endif + +#ifndef LOWORD +#define MAKEWORD(a, b) ((unsigned short)(((BYTE)(a)) | ((WORD)((BYTE)(b))) << 8)) +#define MAKELONG(a, b) ((int)(((unsigned short)(a)) | ((DWORD)((unsigned short)(b))) << 16)) +#define MAKEWPARAM(l, h) (WPARAM)MAKELONG(l, h) +#define MAKELPARAM(l, h) (LPARAM)MAKELONG(l, h) +#define MAKELRESULT(l, h) (LRESULT)MAKELONG(l, h) +#define LOWORD(l) ((unsigned short)(l)) +#define HIWORD(l) ((unsigned short)(((unsigned int)(l) >> 16) & 0xFFFF)) +#define LOBYTE(w) ((BYTE)(w)) +#define HIBYTE(w) ((BYTE)(((unsigned short)(w) >> 8) & 0xFF)) +#endif + +#define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp)) +#define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp)) + +#define UNREFERENCED_PARAMETER(P) (P) +#define _T(T) T + +#define CallWindowProc(A,B,C,D,E) ((WNDPROC)A)(B,C,D,E) +#define OffsetRect WinOffsetRect //to avoid OSX's OffsetRect function +#define SetRect WinSetRect //to avoid OSX's SetRect function +#define UnionRect WinUnionRect +#define IntersectRect WinIntersectRect + + +#define MAX_PATH 1024 + + +#if !defined(max) && !defined(WDL_NO_DEFINE_MINMAX) && !defined(NOMINMAX) +#define max(x,y) ((x)<(y)?(y):(x)) +#define min(x,y) ((x)<(y)?(x):(y)) +#endif + +// SWELLAPP stuff (swellappmain.mm) +#ifdef __cplusplus +extern "C" { +#endif +INT_PTR SWELLAppMain(int msg, INT_PTR parm1, INT_PTR parm2); // to be implemented by app (if using swellappmain.mm) +#ifdef __cplusplus +}; +#endif + + +#if defined(__APPLE__) && !defined(SWELL_USE_OBJC_BOOL) + #include + // this may be safe to always use, but for now only use when using a very very modern SDK + #ifdef MAC_OS_X_VERSION_10_16 + #define SWELL_USE_OBJC_BOOL + #endif +#endif + +// basic types +#ifdef SWELL_USE_OBJC_BOOL + #include + #ifndef __OBJC__ + #undef NO + #undef YES + #undef Nil + #undef nil + #endif +#else + typedef signed char BOOL; +#endif +typedef unsigned char BYTE; +typedef unsigned short WORD; +typedef unsigned int DWORD; +typedef DWORD COLORREF; +typedef unsigned int UINT; +typedef int INT; + +typedef ULONG_PTR WPARAM; +typedef LONG_PTR LPARAM; +typedef LONG_PTR LRESULT; + + +typedef void *LPVOID, *PVOID; + +#if defined(__APPLE__) && !defined(__LP64__) +typedef signed long HRESULT; +typedef signed long LONG; +typedef unsigned long ULONG; +#else +typedef signed int HRESULT; +typedef signed int LONG; +typedef unsigned int ULONG; +#endif + +typedef short SHORT; +typedef int *LPINT; +typedef char CHAR; +typedef char *LPSTR, *LPTSTR; +typedef const char *LPCSTR, *LPCTSTR; + +#define __int64 long long // define rather than typedef, for unsigned __int64 support + +typedef unsigned __int64 ULONGLONG; + +typedef union { + unsigned long long QuadPart; + struct { + #ifdef __ppc__ + DWORD HighPart; + DWORD LowPart; + #else + DWORD LowPart; + DWORD HighPart; + #endif + }; +} ULARGE_INTEGER; + + +typedef struct HWND__ *HWND; +typedef struct HMENU__ *HMENU; +typedef void *HANDLE, *HINSTANCE, *HDROP; +typedef void *HGLOBAL; + +typedef void (*TIMERPROC)(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime); + +typedef struct +{ + LONG x,y; +} POINT, *LPPOINT; + + +typedef struct +{ + SHORT x; + SHORT y; +} POINTS; + + +typedef struct +{ + LONG left,top, right, bottom; +} RECT, *LPRECT; + + +typedef struct { + unsigned char fVirt; + unsigned short key,cmd; +} ACCEL, *LPACCEL; + + +typedef struct { + DWORD dwLowDateTime; + DWORD dwHighDateTime; +} FILETIME; + +typedef struct _GUID { + unsigned int Data1; + unsigned short Data2; + unsigned short Data3; + unsigned char Data4[8]; +} GUID; + +typedef struct { + HWND hwnd; + UINT message; + WPARAM wParam; + LPARAM lParam; + DWORD time; + POINT pt; +} MSG, *LPMSG; + +typedef struct HDC__ *HDC; +typedef struct HCURSOR__ *HCURSOR; +typedef struct HRGN__ *HRGN; + +typedef struct HGDIOBJ__ *HBITMAP; +typedef struct HGDIOBJ__ *HICON; +typedef struct HGDIOBJ__ *HGDIOBJ; +typedef struct HGDIOBJ__ *HBRUSH; +typedef struct HGDIOBJ__ *HPEN; +typedef struct HGDIOBJ__ *HFONT; + + +typedef struct +{ + HWND hwndFrom; + UINT_PTR idFrom; + UINT code; +} NMHDR, *LPNMHDR; + + +typedef struct { + NMHDR hdr; + DWORD_PTR dwItemSpec; + DWORD_PTR dwItemData; + POINT pt; + DWORD dwHitInfo; +} NMMOUSE, *LPNMMOUSE; +typedef NMMOUSE NMCLICK; +typedef LPNMMOUSE LPNMCLICK; + +typedef struct +{ + int mask, fmt,cx; + char *pszText; + int cchTextMax, iSubItem; +} LVCOLUMN; +typedef struct +{ + int mask, iItem, iSubItem, state, stateMask; + char *pszText; + int cchTextMax, iImage; + LPARAM lParam; +} LVITEM; + +typedef int (*PFNLVCOMPARE)(LPARAM, LPARAM, LPARAM); + +typedef struct HIMAGELIST__ *HIMAGELIST; + +typedef struct +{ + POINT pt; + UINT flags; + int iItem; + int iSubItem; // this is was NOT in win95. valid only for LVM_SUBITEMHITTEST +} LVHITTESTINFO, *LPLVHITTESTINFO; + + +typedef struct +{ + NMHDR hdr; + int iItem; + int iSubItem; + UINT uNewState; + UINT uOldState; + UINT uChanged; + POINT ptAction; + LPARAM lParam; +} NMLISTVIEW, *LPNMLISTVIEW; + +typedef struct +{ + NMHDR hdr; + LVITEM item; +} NMLVDISPINFO, *LPNMLVDISPINFO; + +typedef struct +{ + UINT mask; + int cxy; + char* pszText; + HBITMAP hbm; + int cchTextMax; + int fmt; + LPARAM lParam; + int iImage; + int iOrder; + UINT type; + void *pvFilter; + UINT state; +} HDITEM, *LPHDITEM; + +typedef struct TCITEM +{ + UINT mask; + DWORD dwState; + DWORD dwStateMask; + char *pszText; + int cchTextMax; + int iImage; + + LPARAM lParam; +} TCITEM, *LPTCITEM; + +typedef struct tagDRAWITEMSTRUCT { + UINT CtlType; + UINT CtlID; + UINT itemID; + UINT itemAction; + UINT itemState; + HWND hwndItem; + HDC hDC; + RECT rcItem; + DWORD_PTR itemData; +} DRAWITEMSTRUCT, *PDRAWITEMSTRUCT, *LPDRAWITEMSTRUCT; + +typedef struct tagBITMAP { + LONG bmWidth; + LONG bmHeight; + LONG bmWidthBytes; + WORD bmPlanes; + WORD bmBitsPixel; + LPVOID bmBits; +} BITMAP, *PBITMAP, *LPBITMAP; +#define ODT_MENU 1 +#define ODT_LISTBOX 2 +#define ODT_COMBOBOX 3 +#define ODT_BUTTON 4 + +#define ODS_SELECTED 0x0001 + + + + +typedef struct +{ + DWORD cbSize; + HWND hWnd; + UINT uID; + UINT uFlags; + UINT uCallbackMessage; + HICON hIcon; + CHAR szTip[64]; +} NOTIFYICONDATA,*PNOTIFYICONDATA, *LPNOTIFYICONDATA; + + +#define NIM_ADD 0x00000000 +#define NIM_MODIFY 0x00000001 +#define NIM_DELETE 0x00000002 + +#define NIF_MESSAGE 0x00000001 +#define NIF_ICON 0x00000002 +#define NIF_TIP 0x00000004 + + + +typedef struct HTREEITEM__ *HTREEITEM; + +#define TVIF_TEXT 0x0001 +#define TVIF_IMAGE 0x0002 +#define TVIF_PARAM 0x0004 +#define TVIF_STATE 0x0008 +#define TVIF_HANDLE 0x0010 +#define TVIF_SELECTEDIMAGE 0x0020 +#define TVIF_CHILDREN 0x0040 + +#define TVIS_SELECTED 0x0002 +#define TVIS_DROPHILITED 0x0008 +#define TVIS_BOLD 0x0010 +#define TVIS_EXPANDED 0x0020 + +#define TVE_COLLAPSE 0x0001 +#define TVE_EXPAND 0x0002 +#define TVE_TOGGLE 0x0003 + +#define TVN_FIRST (0U-400U) // treeview +#define TVN_SELCHANGED (TVN_FIRST-2) +#define TVN_ITEMEXPANDING (TVN_FIRST-5) + +// swell-extension: WM_MOUSEMOVE set via capture in TVN_BEGINDRAG can return: +// -1 = drag not possible +// -2 = destination at end of list +// (HTREEITEM) = will end up before this item +#define TVN_BEGINDRAG (TVN_FIRST-7) + +#define TVI_ROOT ((HTREEITEM)0xFFFF0000) +#define TVI_FIRST ((HTREEITEM)0xFFFF0001) +#define TVI_LAST ((HTREEITEM)0xFFFF0002) +#define TVI_SORT ((HTREEITEM)0xFFFF0003) + +#define TVHT_NOWHERE 0x0001 +#define TVHT_ONITEMICON 0x0002 +#define TVHT_ONITEMLABEL 0x0004 +#define TVHT_ONITEM (TVHT_ONITEMICON | TVHT_ONITEMLABEL | TVHT_ONITEMSTATEICON) +#define TVHT_ONITEMINDENT 0x0008 +#define TVHT_ONITEMBUTTON 0x0010 +#define TVHT_ONITEMRIGHT 0x0020 +#define TVHT_ONITEMSTATEICON 0x0040 + +#define TVHT_ABOVE 0x0100 +#define TVHT_BELOW 0x0200 +#define TVHT_TORIGHT 0x0400 +#define TVHT_TOLEFT 0x0800 + +typedef struct { + UINT mask; + HTREEITEM hItem; + UINT state; + UINT stateMask; + char *pszText; + int cchTextMax; + int iImage; + int iSelectedImage; + int cChildren; + LPARAM lParam; +} TVITEM, TV_ITEM, *LPTVITEM, *LPTV_ITEM; + +typedef struct { + HTREEITEM hParent; + HTREEITEM hInsertAfter; + TVITEM item; +} TVINSERTSTRUCT, *LPTVINSERTSTRUCT, TV_INSERTSTRUCT, *LPTV_INSERTSTRUCT; + +typedef struct { + POINT pt; + UINT flags; + HTREEITEM hItem; +} TVHITTESTINFO, *LPTVHITTESTINFO; + +typedef struct { + NMHDR hdr; + UINT action; + TVITEM itemOld; + TVITEM itemNew; + POINT ptDrag; +} NMTREEVIEW, *LPNMTREEVIEW; + + +typedef struct +{ + unsigned int cbSize, fMask, fType, fState, wID; + HMENU hSubMenu; + HICON hbmpChecked,hbmpUnchecked; + DWORD_PTR dwItemData; + char *dwTypeData; + int cch; + HBITMAP hbmpItem; +} MENUITEMINFO; + +#define SetMenuDefaultItem(a,b,c) do { if ((a)||(b)||(c)) { } } while(0) + +typedef struct { + POINT ptReserved, ptMaxSize, ptMaxPosition, ptMinTrackSize, ptMaxTrackSize; +} MINMAXINFO, *LPMINMAXINFO; + + +typedef struct +{ + int lfHeight, lfWidth, lfEscapement,lfOrientation, lfWeight; + char lfItalic, lfUnderline, lfStrikeOut, lfCharSet, lfOutPrecision, lfClipPrecision, + lfQuality, lfPitchAndFamily; + char lfFaceName[32]; +} LOGFONT; +typedef struct +{ + LONG tmHeight; + LONG tmAscent; + LONG tmDescent; + LONG tmInternalLeading; + LONG tmAveCharWidth; + // todo: implement rest +} TEXTMETRIC; + +typedef struct { + HDC hdc; + BOOL fErase; + RECT rcPaint; +} PAINTSTRUCT; + +typedef struct +{ + UINT cbSize; + UINT fMask; + int nMin; + int nMax; + UINT nPage; + int nPos; + int nTrackPos; +} SCROLLINFO, *LPSCROLLINFO; + +typedef struct +{ + DWORD styleOld; + DWORD styleNew; +} STYLESTRUCT, *LPSTYLESTRUCT; + +typedef struct _DROPFILES { + DWORD pFiles; // offset of file list + POINT pt; // drop point (client coords) + BOOL fNC; // is it on NonClient area + // and pt is in screen coords + BOOL fWide; // WIDE character switch +} DROPFILES, *LPDROPFILES; + + +typedef struct +{ + HWND hwnd; + HWND hwndInsertAfter; + int x; + int y; + int cx; + int cy; + UINT flags; +} WINDOWPOS, *LPWINDOWPOS, *PWINDOWPOS; + +typedef struct +{ + RECT rgrc[3]; + PWINDOWPOS lppos; +} NCCALCSIZE_PARAMS, *LPNCCALCSIZE_PARAMS; + + + +typedef INT_PTR (*DLGPROC)(HWND, UINT, WPARAM, LPARAM); +typedef LRESULT (*WNDPROC)(HWND, UINT, WPARAM, LPARAM); + + + +#define GF_BEGIN 1 +#define GF_INERTIA 2 +#define GF_END 4 + +#define GID_BEGIN 1 +#define GID_END 2 +#define GID_ZOOM 3 +#define GID_PAN 4 +#define GID_ROTATE 5 +#define GID_TWOFINGERTAP 6 +#define GID_ROLLOVER 7 + +typedef struct tagGESTUREINFO +{ + UINT cbSize; + DWORD dwFlags; + DWORD dwID; + HWND hwndTarget; + POINTS ptsLocation; + DWORD dwInstanceID; + DWORD dwSequenceID; + ULONGLONG ullArguments; + UINT cbExtraArgs; +} GESTUREINFO; + +// not using this stuff yet +#define GC_PAN 1 +#define GC_PAN_WITH_SINGLE_FINGER_VERTICALLY 2 +#define GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY 4 + +typedef struct tagGESTURECONFIG +{ + DWORD dwID; + DWORD dwWant; + DWORD dwBlock; +} GESTURECONFIG; + + + +#ifndef WINAPI +#define WINAPI +#endif + +#ifndef CALLBACK +#define CALLBACK +#endif + + +typedef BOOL (*PROPENUMPROCEX)(HWND hwnd, const char *lpszString, HANDLE hData, LPARAM lParam); + +// swell specific type +typedef HWND (*SWELL_ControlCreatorProc)(HWND parent, const char *cname, int idx, const char *classname, int style, int x, int y, int w, int h); + +#define DLL_PROCESS_DETACH 0 +#define DLL_PROCESS_ATTACH 1 + +// if the user implements this (and links with swell-modstub[-generic], this will get called for DLL_PROCESS_[AT|DE]TACH +#ifdef __cplusplus +extern "C" { +#endif +__attribute__ ((visibility ("default"))) BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD fdwReason, LPVOID lpvReserved); +#ifdef __cplusplus +}; +#endif + +/* + ** win32 specific constants + */ +#define MB_OK 0 +#define MB_OKCANCEL 1 +#define MB_ABORTRETRYIGNORE 2 +#define MB_YESNOCANCEL 3 +#define MB_YESNO 4 +#define MB_RETRYCANCEL 5 + +#define MB_DEFBUTTON1 0 +#define MB_DEFBUTTON2 0x00000100 +#define MB_DEFBUTTON3 0x00000200 + +#define MB_ICONERROR 0 +#define MB_ICONSTOP 0 +#define MB_ICONINFORMATION 0 +#define MB_ICONWARNING 0 +#define MB_ICONQUESTION 0 +#define MB_TOPMOST 0 +#define MB_ICONEXCLAMATION 0 + +#define IDOK 1 +#define IDCANCEL 2 +#define IDABORT 3 +#define IDRETRY 4 +#define IDIGNORE 5 +#define IDYES 6 +#define IDNO 7 + +#define GW_HWNDFIRST 0 +#define GW_HWNDLAST 1 +#define GW_HWNDNEXT 2 +#define GW_HWNDPREV 3 +#define GW_OWNER 4 +#define GW_CHILD 5 + +#define GWL_HWNDPARENT (-25) +#define GWL_USERDATA (-21) +#define GWL_ID (-12) +#define GWL_STYLE (-16) // only supported for BS_ for now I think +#define GWL_EXSTYLE (-20) +#define GWL_WNDPROC (-4) +#define DWL_DLGPROC (-8) + +#define SWELL_NOT_WS_VISIBLE ((int)0x80000000) +// oops these don't match real windows +#define WS_CHILDWINDOW (WS_CHILD) +#define WS_CHILD 0x40000000L +#define WS_DISABLED 0x08000000L +#define WS_CLIPSIBLINGS 0x04000000L +#define WS_VISIBLE 0x02000000L // only used by GetWindowLong(GWL_STYLE) -- not settable +#define WS_CAPTION 0x00C00000L +#define WS_VSCROLL 0x00200000L +#define WS_HSCROLL 0x00100000L +#define WS_SYSMENU 0x00080000L +#define WS_THICKFRAME 0x00040000L +#define WS_GROUP 0x00020000L +#define WS_TABSTOP 0x00010000L + +#define TVS_DISABLEDRAGDROP 0x10 + +#define WS_BORDER 0 // ignored for now + +#define WM_CTLCOLORMSGBOX 0x0132 +#define WM_CTLCOLOREDIT 0x0133 +#define WM_CTLCOLORLISTBOX 0x0134 +#define WM_CTLCOLORBTN 0x0135 +#define WM_CTLCOLORDLG 0x0136 +#define WM_CTLCOLORSCROLLBAR 0x0137 +#define WM_CTLCOLORSTATIC 0x0138 + +#define CB_ADDSTRING 0x0143 +#define CB_DELETESTRING 0x0144 +#define CB_GETCOUNT 0x0146 +#define CB_GETCURSEL 0x0147 +#define CB_GETLBTEXT 0x0148 +#define CB_GETLBTEXTLEN 0x0149 +#define CB_INSERTSTRING 0x014A +#define CB_RESETCONTENT 0x014B +#define CB_FINDSTRING 0x014C +#define CB_SETCURSEL 0x014E +#define CB_GETITEMDATA 0x0150 +#define CB_SETITEMDATA 0x0151 +#define CB_FINDSTRINGEXACT 0x0158 +#define CB_INITSTORAGE 0x0161 + +#define LB_ADDSTRING 0x0180 // oops these don't all match real windows, todo fix (maybe) +#define LB_INSERTSTRING 0x0181 +#define LB_DELETESTRING 0x0182 +#define LB_GETTEXT 0x0183 +#define LB_RESETCONTENT 0x0184 +#define LB_SETSEL 0x0185 +#define LB_SETCURSEL 0x0186 +#define LB_GETSEL 0x0187 +#define LB_GETCURSEL 0x0188 +#define LB_GETTEXTLEN 0x018A +#define LB_GETCOUNT 0x018B +#define LB_GETSELCOUNT 0x0190 +#define LB_GETITEMDATA 0x0199 +#define LB_SETITEMDATA 0x019A +#define LB_FINDSTRINGEXACT 0x01A2 + +#define TBM_GETPOS (WM_USER) +#define TBM_SETTIC (WM_USER+4) +#define TBM_SETPOS (WM_USER+5) +#define TBM_SETRANGE (WM_USER+6) +#define TBM_SETSEL (WM_USER+10) + +#define PBM_SETRANGE (WM_USER+1) +#define PBM_SETPOS (WM_USER+2) +#define PBM_DELTAPOS (WM_USER+3) + +#define BM_GETCHECK 0x00F0 +#define BM_SETCHECK 0x00F1 +#define BM_GETIMAGE 0x00F6 +#define BM_SETIMAGE 0x00F7 +#define IMAGE_BITMAP 0 +#define IMAGE_ICON 1 + +#define NM_FIRST (0U- 0U) // generic to all controls +#define NM_LAST (0U- 99U) +#define NM_CLICK (NM_FIRST-2) // uses NMCLICK struct +#define NM_DBLCLK (NM_FIRST-3) +#define NM_RCLICK (NM_FIRST-5) // uses NMCLICK struct +#define NM_CUSTOMDRAW (NM_FIRST-12) + + +#define LVSIL_STATE 1 +#define LVSIL_SMALL 2 + +#define LVIR_BOUNDS 0 +#define LVIR_ICON 1 +#define LVIR_LABEL 2 +#define LVIR_SELECTBOUNDS 3 + + +#define LVHT_NOWHERE 0x0001 +#define LVHT_ONITEMICON 0x0002 +#define LVHT_ONITEMLABEL 0x0004 +#define LVHT_ONITEMSTATEICON 0x0008 +#define LVHT_ONITEM (LVHT_ONITEMICON | LVHT_ONITEMLABEL | LVHT_ONITEMSTATEICON) + +#define LVHT_ABOVE 0x0010 +#define LVHT_BELOW 0x0020 +#define LVHT_TORIGHT 0x0040 +#define LVHT_TOLEFT 0x0080 + +#define LVCF_FMT 1 +#define LVCF_WIDTH 2 +#define LVCF_TEXT 4 + +#define LVCFMT_LEFT 0 +#define LVCFMT_RIGHT 1 +#define LVCFMT_CENTER 2 + +#define LVIF_TEXT 1 +#define LVIF_IMAGE 2 +#define LVIF_PARAM 4 +#define LVIF_STATE 8 + +#define LVIS_SELECTED 1 +#define LVIS_FOCUSED 2 +#define LVNI_SELECTED 1 +#define LVNI_FOCUSED 2 +#define INDEXTOSTATEIMAGEMASK(x) ((x)<<16) +#define LVIS_STATEIMAGEMASK (255<<16) + +#define LVN_FIRST (0U-100U) // listview +#define LVN_LAST (0U-199U) +#define LVN_BEGINDRAG (LVN_FIRST-9) +#define LVN_COLUMNCLICK (LVN_FIRST-8) +#define LVN_ITEMCHANGED (LVN_FIRST-1) +#define LVN_ODFINDITEM (LVN_FIRST-52) +#define LVN_GETDISPINFO (LVN_FIRST-50) + +#define LVS_EX_GRIDLINES 0x01 +#define LVS_EX_SUBITEMIMAGES 0x02 +#define LVS_EX_HEADERDRAGDROP 0x10 +#define LVS_EX_FULLROWSELECT 0x20 // ignored for now (enabled by default on OSX) + +#define HDI_FORMAT 0x4 +#define HDF_SORTUP 0x0400 +#define HDF_SORTDOWN 0x0200 + +#define TCIF_TEXT 0x0001 +#define TCIF_IMAGE 0x0002 +#define TCIF_PARAM 0x0008 +//#define TCIF_STATE 0x0010 + + + +#define TCN_FIRST (0U-550U) // tab control +#define TCN_LAST (0U-580U) +#define TCN_SELCHANGE (TCN_FIRST - 1) + + +#define BS_AUTOCHECKBOX 0x00000003L +#define BS_AUTO3STATE 0x00000006L +#define BS_AUTORADIOBUTTON 0x00000009L +#define BS_OWNERDRAW 0x0000000BL +#define BS_BITMAP 0x00000080L + + + +#define BST_CHECKED 1 +#define BST_UNCHECKED 0 +#define BST_INDETERMINATE 2 + +// note: these differ in values from their win32 counterparts, because we got them +// wrong to begin with, and we'd like to keep backwards compatibility for things compiled +// against an old swell.h (and using the SWELL API via an exported mechanism, i.e. third party +// plug-ins). +#define SW_HIDE 0 +#define SW_SHOWNA 1 // 8 on win32 +#define SW_SHOW 2 // 1 on win32 +#define SW_SHOWMINIMIZED 3 // 2 on win32 +#define SW_SHOWMAXIMIZED 4 +#define SW_RESTORE 5 + +// aliases (todo implement these as needed) +#define SW_SHOWNOACTIVATE SW_SHOWNA +#define SW_NORMAL SW_SHOW +#define SW_SHOWNORMAL SW_SHOW +#define SW_SHOWDEFAULT SW_SHOWNORMAL + +#define SWP_NOMOVE 1 +#define SWP_NOSIZE 2 +#define SWP_NOZORDER 4 +#define SWP_NOACTIVATE 8 +#define SWP_SHOWWINDOW 16 +#define SWP_FRAMECHANGED 32 +#define SWP_NOCOPYBITS 0 +#define HWND_TOP ((HWND)0) +#define HWND_BOTTOM ((HWND)1) +#define HWND_TOPMOST ((HWND)-1) +#define HWND_NOTOPMOST ((HWND)-2) + +// most of these are ignored, actually, but TPM_NONOTIFY and TPM_RETURNCMD are now used +#define TPM_LEFTBUTTON 0x0000L +#define TPM_RIGHTBUTTON 0x0002L +#define TPM_LEFTALIGN 0x0000L +#define TPM_CENTERALIGN 0x0004L +#define TPM_RIGHTALIGN 0x0008L +#define TPM_TOPALIGN 0x0000L +#define TPM_VCENTERALIGN 0x0010L +#define TPM_BOTTOMALIGN 0x0020L +#define TPM_HORIZONTAL 0x0000L /* Horz alignment matters more */ +#define TPM_VERTICAL 0x0040L /* Vert alignment matters more */ +#define TPM_NONOTIFY 0x0080L /* Don't send any notification msgs */ +#define TPM_RETURNCMD 0x0100L + +#define MIIM_ID 1 +#define MIIM_STATE 2 +#define MIIM_TYPE 4 +#define MIIM_SUBMENU 8 +#define MIIM_DATA 16 +#define MIIM_BITMAP 0x80 +#ifdef __APPLE__ +#define MIIM_SWELL_DO_NOT_CALC_MODIFIERS (1<<30) +#endif + +#define MF_ENABLED 0 +#define MF_GRAYED 1 +#define MF_DISABLED 2 +#define MF_STRING 0 +#define MF_BITMAP 4 +#define MF_UNCHECKED 0 +#define MF_CHECKED 8 +#define MF_POPUP 0x10 +#define MF_BYCOMMAND 0 +#define MF_BYPOSITION 0x400 +#define MF_SEPARATOR 0x800 +#ifdef __APPLE__ +#define MF_SWELL_DO_NOT_CALC_MODIFIERS (1<<30) +#endif + +#define MFT_STRING MF_STRING +#define MFT_BITMAP MF_BITMAP +#define MFT_SEPARATOR MF_SEPARATOR +#define MFT_RADIOCHECK 0x200 + +#define MFS_GRAYED (MF_GRAYED|MF_DISABLED) +#define MFS_DISABLED MFS_GRAYED +#define MFS_CHECKED MF_CHECKED +#define MFS_ENABLED MF_ENABLED +#define MFS_UNCHECKED MF_UNCHECKED + +#define EN_SETFOCUS 0x0100 +#define EN_KILLFOCUS 0x0200 +#define EN_CHANGE 0x0300 +#define STN_CLICKED 0 +#define STN_DBLCLK 1 +#define WM_CREATE 0x0001 +#define WM_DESTROY 0x0002 +#define WM_MOVE 0x0003 +#define WM_SIZE 0x0005 +#define WM_ACTIVATE 0x0006 +#define WM_SETFOCUS 0x0007 +#define WM_KILLFOCUS 0x0008 +#define WM_SETREDRAW 0x000B // implemented on macOS NSTableViews, maybe elsewhere? +#define WM_SETTEXT 0x000C // not implemented on OSX, used internally on Linux +#define WM_PAINT 0x000F +#define WM_CLOSE 0x0010 +#define WM_ERASEBKGND 0x0014 +#define WM_SHOWWINDOW 0x0018 +#define WM_ACTIVATEAPP 0x001C +#define WM_SETCURSOR 0x0020 +#define WM_MOUSEACTIVATE 0x0021 +#define WM_GETMINMAXINFO 0x0024 +#define WM_DRAWITEM 0x002B +#define WM_SETFONT 0x0030 +#define WM_GETFONT 0x0031 +#define WM_GETOBJECT 0x003D // implemented differently than win32 -- see virtwnd/virtwnd-nsaccessibility.mm +#define WM_COPYDATA 0x004A +#define WM_NOTIFY 0x004E +#define WM_CONTEXTMENU 0x007B +#define WM_STYLECHANGED 0x007D +#define WM_DISPLAYCHANGE 0x007E +#define WM_NCDESTROY 0x0082 +#define WM_NCCALCSIZE 0x0083 +#define WM_NCHITTEST 0x0084 +#define WM_NCPAINT 0x0085 +#define WM_NCMOUSEMOVE 0x00A0 +#define WM_NCLBUTTONDOWN 0x00A1 +#define WM_NCLBUTTONUP 0x00A2 +#define WM_NCLBUTTONDBLCLK 0x00A3 +#define WM_NCRBUTTONDOWN 0x00A4 +#define WM_NCRBUTTONUP 0x00A5 +#define WM_NCRBUTTONDBLCLK 0x00A6 +#define WM_NCMBUTTONDOWN 0x00A7 +#define WM_NCMBUTTONUP 0x00A8 +#define WM_NCMBUTTONDBLCLK 0x00A9 +#define WM_KEYFIRST 0x0100 +#define WM_KEYDOWN 0x0100 +#define WM_KEYUP 0x0101 +#define WM_CHAR 0x0102 +#define WM_DEADCHAR 0x0103 +#define WM_SYSKEYDOWN 0x0104 +#define WM_SYSKEYUP 0x0105 +#define WM_SYSCHAR 0x0106 +#define WM_SYSDEADCHAR 0x0107 +#define WM_KEYLAST 0x0108 +#define WM_INITDIALOG 0x0110 +#define WM_COMMAND 0x0111 +#define WM_SYSCOMMAND 0x0112 +#define WM_TIMER 0x0113 +#define WM_HSCROLL 0x0114 +#define WM_VSCROLL 0x0115 +#define WM_INITMENUPOPUP 0x0117 +#define WM_GESTURE 0x0119 +#define WM_MOUSEFIRST 0x0200 +#define WM_MOUSEMOVE 0x0200 +#define WM_LBUTTONDOWN 0x0201 +#define WM_LBUTTONUP 0x0202 +#define WM_LBUTTONDBLCLK 0x0203 +#define WM_RBUTTONDOWN 0x0204 +#define WM_RBUTTONUP 0x0205 +#define WM_RBUTTONDBLCLK 0x0206 +#define WM_MBUTTONDOWN 0x0207 +#define WM_MBUTTONUP 0x0208 +#define WM_MBUTTONDBLCLK 0x0209 +#define WM_MOUSEWHEEL 0x020A +#define WM_MOUSEHWHEEL 0x020E +#define WM_MOUSELAST 0x020A +#define WM_CAPTURECHANGED 0x0215 +#define WM_DROPFILES 0x0233 +#define WM_SWELL_EXTENDED 0x0399 /* wParam = message specific type */ +#define WM_USER 0x0400 + +#define SC_CLOSE 0xF060 + +#define HTTRANSPARENT (-1) +#define HTCAPTION 2 +#define HTBOTTOMRIGHT 17 + +#define WA_INACTIVE 0 +#define WA_ACTIVE 1 +#define WA_CLICKACTIVE 2 + +#define BN_CLICKED 0 + +#define LBN_SELCHANGE 1 +#define LBN_DBLCLK 2 +#define LB_ERR (-1) + +#define CBN_SELCHANGE 1 +#define CBN_EDITCHANGE 5 +#define CBN_DROPDOWN 7 +#define CBN_CLOSEUP 8 +#define CB_ERR (-1) + +#define EM_GETSEL 0xF0B0 +#define EM_SETSEL 0xF0B1 +#define EM_SCROLL 0xF0B5 +#define EM_REPLACESEL 0xF0C2 +#define EM_SETPASSWORDCHAR 0xF0CC + +#define SB_HORZ 0 +#define SB_VERT 1 +#define SB_CTL 2 +#define SB_BOTH 3 + +#define SB_LINEUP 0 +#define SB_LINELEFT 0 +#define SB_LINEDOWN 1 +#define SB_LINERIGHT 1 +#define SB_PAGEUP 2 +#define SB_PAGELEFT 2 +#define SB_PAGEDOWN 3 +#define SB_PAGERIGHT 3 +#define SB_THUMBPOSITION 4 +#define SB_THUMBTRACK 5 +#define SB_TOP 6 +#define SB_LEFT 6 +#define SB_BOTTOM 7 +#define SB_RIGHT 7 +#define SB_ENDSCROLL 8 + +#define DFCS_SCROLLUP 0x0000 +#define DFCS_SCROLLDOWN 0x0001 +#define DFCS_SCROLLLEFT 0x0002 +#define DFCS_SCROLLRIGHT 0x0003 +#define DFCS_SCROLLCOMBOBOX 0x0005 +#define DFCS_SCROLLSIZEGRIP 0x0008 +#define DFCS_SCROLLSIZEGRIPRIGHT 0x0010 + +#define DFCS_INACTIVE 0x0100 +#define DFCS_PUSHED 0x0200 +#define DFCS_CHECKED 0x0400 +#define DFCS_FLAT 0x4000 + +#define DFCS_BUTTONPUSH 0x0010 + +#define DFC_SCROLL 3 +#define DFC_BUTTON 4 + +#define ESB_ENABLE_BOTH 0x0000 +#define ESB_DISABLE_BOTH 0x0003 + +#define ESB_DISABLE_LEFT 0x0001 +#define ESB_DISABLE_RIGHT 0x0002 + +#define ESB_DISABLE_UP 0x0001 +#define ESB_DISABLE_DOWN 0x0002 + +#define BDR_RAISEDOUTER 0x0001 +#define BDR_SUNKENOUTER 0x0002 +#define BDR_RAISEDINNER 0x0004 +#define BDR_SUNKENINNER 0x0008 + +#define BDR_OUTER 0x0003 +#define BDR_INNER 0x000c + +#define EDGE_RAISED (BDR_RAISEDOUTER | BDR_RAISEDINNER) +#define EDGE_SUNKEN (BDR_SUNKENOUTER | BDR_SUNKENINNER) +#define EDGE_ETCHED (BDR_SUNKENOUTER | BDR_RAISEDINNER) +#define EDGE_BUMP (BDR_RAISEDOUTER | BDR_SUNKENINNER) + +#define BF_ADJUST 0x2000 +#define BF_FLAT 0x4000 +#define BF_LEFT 0x0001 +#define BF_TOP 0x0002 +#define BF_RIGHT 0x0004 +#define BF_BOTTOM 0x0008 +#define BF_RECT (BF_LEFT | BF_TOP | BF_RIGHT | BF_BOTTOM) + +#define PATCOPY (DWORD)0x00F00021 + +#define HTHSCROLL 6 +#define HTVSCROLL 7 + +#define WS_EX_LEFTSCROLLBAR 0x00004000L +#define WS_EX_ACCEPTFILES 0x00000010L + +#define SIF_RANGE 0x0001 +#define SIF_PAGE 0x0002 +#define SIF_POS 0x0004 +#define SIF_DISABLENOSCROLL 0x0008 +#define SIF_TRACKPOS 0x0010 +#define SIF_ALL (SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS) + +#define SIZE_RESTORED 0 +#define SIZE_MINIMIZED 1 +#define SIZE_MAXIMIZED 2 +#define SIZE_MAXSHOW 3 +#define SIZE_MAXHIDE 4 + +typedef struct tagNMLVCUSTOMDRAW +{ + struct { + NMHDR hdr; + DWORD dwDrawStage; + HDC hdc; // not implemented + RECT rc; // not implemented + DWORD dwItemSpec; + UINT uItemState; // not implemented + LPARAM lItemlParam; // not implemented + } nmcd; + + COLORREF clrText, clrTextBk; + int iSubItem; +} NMLVCUSTOMDRAW, *LPNMLVCUSTOMDRAW; +// only currently used by listviews for color override +#define CDDS_PREPAINT (0x00001) +#define CDDS_ITEM (0x10000) +#define CDDS_ITEMPREPAINT (CDDS_ITEM | CDDS_PREPAINT) + +#ifndef MAKEINTRESOURCE +#define MAKEINTRESOURCE(x) ((const char *)(UINT_PTR)(x)) +#endif + +#ifdef FSHIFT +#undef FSHIFT +#endif + +#define FVIRTKEY 1 +#define FSHIFT 0x04 +#define FCONTROL 0x08 +#define FALT 0x10 +#define FLWIN 0x20 + + +#define VK_LBUTTON 0x01 +#define VK_RBUTTON 0x02 +#define VK_MBUTTON 0x04 + +#define VK_BACK 0x08 +#define VK_TAB 0x09 + +#define VK_CLEAR 0x0C +#define VK_RETURN 0x0D + +#define VK_SHIFT 0x10 +#define VK_CONTROL 0x11 +#define VK_MENU 0x12 +#define VK_PAUSE 0x13 +#define VK_CAPITAL 0x14 + +#define VK_ESCAPE 0x1B + +#define VK_SPACE 0x20 +#define VK_PRIOR 0x21 +#define VK_NEXT 0x22 +#define VK_END 0x23 +#define VK_HOME 0x24 +#define VK_LEFT 0x25 +#define VK_UP 0x26 +#define VK_RIGHT 0x27 +#define VK_DOWN 0x28 +#define VK_SELECT 0x29 +#define VK_PRINT 0x2A +#define VK_SNAPSHOT 0x2C +#define VK_INSERT 0x2D +#define VK_DELETE 0x2E +#define VK_HELP 0x2F + +#define VK_LWIN 0x5B + +#define VK_NUMPAD0 0x60 +#define VK_NUMPAD1 0x61 +#define VK_NUMPAD2 0x62 +#define VK_NUMPAD3 0x63 +#define VK_NUMPAD4 0x64 +#define VK_NUMPAD5 0x65 +#define VK_NUMPAD6 0x66 +#define VK_NUMPAD7 0x67 +#define VK_NUMPAD8 0x68 +#define VK_NUMPAD9 0x69 +#define VK_MULTIPLY 0x6A +#define VK_ADD 0x6B +#define VK_SEPARATOR 0x6C +#define VK_SUBTRACT 0x6D +#define VK_DECIMAL 0x6E +#define VK_DIVIDE 0x6F +#define VK_F1 0x70 +#define VK_F2 0x71 +#define VK_F3 0x72 +#define VK_F4 0x73 +#define VK_F5 0x74 +#define VK_F6 0x75 +#define VK_F7 0x76 +#define VK_F8 0x77 +#define VK_F9 0x78 +#define VK_F10 0x79 +#define VK_F11 0x7A +#define VK_F12 0x7B +#define VK_F13 0x7C +#define VK_F14 0x7D +#define VK_F15 0x7E +#define VK_F16 0x7F +#define VK_F17 0x80 +#define VK_F18 0x81 +#define VK_F19 0x82 +#define VK_F20 0x83 +#define VK_F21 0x84 +#define VK_F22 0x85 +#define VK_F23 0x86 +#define VK_F24 0x87 + +#define VK_NUMLOCK 0x90 +#define VK_SCROLL 0x91 + +// these should probably not be used (wParam is not set in WM_LBUTTONDOWN/WM_MOUSEMOVE etc) +#define MK_LBUTTON 0x01 +#define MK_RBUTTON 0x02 +#define MK_MBUTTON 0x10 + +#define IDC_SIZENESW MAKEINTRESOURCE(32643) +#define IDC_SIZENWSE MAKEINTRESOURCE(32642) +#define IDC_IBEAM MAKEINTRESOURCE(32513) +#define IDC_UPARROW MAKEINTRESOURCE(32516) +#define IDC_NO MAKEINTRESOURCE(32648) +#define IDC_SIZEALL MAKEINTRESOURCE(32646) +#define IDC_SIZENS MAKEINTRESOURCE(32645) +#define IDC_SIZEWE MAKEINTRESOURCE(32644) +#define IDC_ARROW MAKEINTRESOURCE(32512) +#define IDC_HAND MAKEINTRESOURCE(32649) + + + +#define COLOR_3DSHADOW 0 +#define COLOR_3DHILIGHT 1 +#define COLOR_3DFACE 2 +#define COLOR_BTNTEXT 3 +#define COLOR_WINDOW 4 +#define COLOR_SCROLLBAR 5 +#define COLOR_3DDKSHADOW 6 +#define COLOR_BTNFACE 7 +#define COLOR_INFOBK 8 +#define COLOR_INFOTEXT 9 + +#define SRCCOPY 0 +#define SRCCOPY_USEALPHACHAN 0xdeadbeef +#define PS_SOLID 0 + +#define DT_TOP 0 +#define DT_LEFT 0 +#define DT_CENTER 1 +#define DT_RIGHT 2 +#define DT_VCENTER 4 +#define DT_BOTTOM 8 +#define DT_WORDBREAK 0x10 +#define DT_SINGLELINE 0x20 +#define DT_NOCLIP 0x100 +#define DT_CALCRECT 0x400 +#define DT_NOPREFIX 0x800 +#define DT_END_ELLIPSIS 0x8000 + +#define FW_DONTCARE 0 +#define FW_THIN 100 +#define FW_EXTRALIGHT 200 +#define FW_LIGHT 300 +#define FW_NORMAL 400 +#define FW_MEDIUM 500 +#define FW_SEMIBOLD 600 +#define FW_BOLD 700 +#define FW_EXTRABOLD 800 +#define FW_HEAVY 900 + +#define FW_ULTRALIGHT FW_EXTRALIGHT +#define FW_REGULAR FW_NORMAL +#define FW_DEMIBOLD FW_SEMIBOLD +#define FW_ULTRABOLD FW_EXTRABOLD +#define FW_BLACK FW_HEAVY + +#define OUT_DEFAULT_PRECIS 0 +#define CLIP_DEFAULT_PRECIS 0 +#define DEFAULT_QUALITY 0 +#define DRAFT_QUALITY 1 +#define PROOF_QUALITY 2 +#define NONANTIALIASED_QUALITY 3 +#define ANTIALIASED_QUALITY 4 +#define DEFAULT_PITCH 0 +#define DEFAULT_CHARSET 0 +#define ANSI_CHARSET 0 +#define TRANSPARENT 0 +#define OPAQUE 1 + +#define NULL_PEN 1 +#define NULL_BRUSH 2 + +#define GGI_MARK_NONEXISTING_GLYPHS 1 + +#define GMEM_ZEROINIT 1 +#define GMEM_FIXED 0 +#define GMEM_MOVEABLE 0 +#define GMEM_DDESHARE 0 +#define GMEM_DISCARDABLE 0 +#define GMEM_SHARE 0 +#define GMEM_LOWER 0 +#define GHND (GMEM_MOVEABLE|GM_ZEROINIT) +#define GPTR (GMEM_FIXED|GMEM_ZEROINIT) + +#define CF_TEXT (1) +#define CF_HDROP (2) + +#define _MCW_RC 0x00000300 /* Rounding Control */ +#define _RC_NEAR 0x00000000 /* near */ +#define _RC_DOWN 0x00000100 /* down */ +#define _RC_UP 0x00000200 /* up */ +#define _RC_CHOP 0x00000300 /* chop */ + + +extern struct SWELL_DialogResourceIndex *SWELL_curmodule_dialogresource_head; +extern struct SWELL_MenuResourceIndex *SWELL_curmodule_menuresource_head; + +#define HTNOWHERE 0 +#define HTCLIENT 1 +#define HTMENU 5 +#define HTHSCROLL 6 +#define HTVSCROLL 7 + +#define SM_CXSCREEN 0 +#define SM_CYSCREEN 1 +#define SM_CXVSCROLL 2 +#define SM_CYHSCROLL 3 +#define SM_CYMENU 15 +#define SM_CYVSCROLL 20 +#define SM_CXHSCROLL 21 + + +#if 0 // these are disabled until implemented + +#define SM_CYCAPTION 4 +#define SM_CXBORDER 5 +#define SM_CYBORDER 6 +#define SM_CXDLGFRAME 7 +#define SM_CYDLGFRAME 8 +#define SM_CYVTHUMB 9 +#define SM_CXHTHUMB 10 +#define SM_CXICON 11 +#define SM_CYICON 12 +#define SM_CXCURSOR 13 +#define SM_CYCURSOR 14 +#define SM_CXFULLSCREEN 16 +#define SM_CYFULLSCREEN 17 +#define SM_CYKANJIWINDOW 18 +#define SM_MOUSEPRESENT 19 +#define SM_DEBUG 22 +#define SM_SWAPBUTTON 23 +#define SM_CXMIN 28 +#define SM_CYMIN 29 +#define SM_CXSIZE 30 +#define SM_CYSIZE 31 +#define SM_CXFRAME 32 +#define SM_CYFRAME 33 +#define SM_CXMINTRACK 34 +#define SM_CYMINTRACK 35 +#define SM_CXDOUBLECLK 36 +#define SM_CYDOUBLECLK 37 +#define SM_CXICONSPACING 38 +#define SM_CYICONSPACING 39 + +#endif // unimplemented system metrics + + +#define THREAD_BASE_PRIORITY_LOWRT 15 +#define THREAD_BASE_PRIORITY_MAX 2 +#define THREAD_BASE_PRIORITY_MIN -2 +#define THREAD_BASE_PRIORITY_IDLE -15 +#define THREAD_PRIORITY_LOWEST THREAD_BASE_PRIORITY_MIN +#define THREAD_PRIORITY_BELOW_NORMAL (THREAD_PRIORITY_LOWEST+1) +#define THREAD_PRIORITY_NORMAL 0 +#define THREAD_PRIORITY_HIGHEST THREAD_BASE_PRIORITY_MAX +#define THREAD_PRIORITY_ABOVE_NORMAL (THREAD_PRIORITY_HIGHEST-1) +#define THREAD_PRIORITY_TIME_CRITICAL THREAD_BASE_PRIORITY_LOWRT +#define THREAD_PRIORITY_IDLE THREAD_BASE_PRIORITY_IDLE + + + +#define WAIT_OBJECT_0 (0 ) +#define WAIT_TIMEOUT (0x00000102L) +#define WAIT_FAILED (DWORD)0xFFFFFFFF +#define INFINITE 0xFFFFFFFF + + +#define FR_PRIVATE 1 // AddFontResourceEx() + +typedef struct _ICONINFO +{ + BOOL fIcon; + DWORD xHotspot; + DWORD yHotspot; + HBITMAP hbmMask; + HBITMAP hbmColor; +} ICONINFO, *PICONINFO; + +typedef struct _COPYDATASTRUCT +{ + ULONG_PTR dwData; + DWORD cbData; + PVOID lpData; +} COPYDATASTRUCT, *PCOPYDATASTRUCT; + +typedef void *HMONITOR; + +typedef struct _MONITORINFO { + DWORD cbSize; + RECT rcMonitor, rcWork; + DWORD dwFlags; +} MONITORINFO, *LPMONITORINFO; + + +typedef struct _MONITORINFOEX { + DWORD cbSize; + RECT rcMonitor, rcWork; + DWORD dwFlags; + char szDevice[256]; +} MONITORINFOEX, *LPMONITORINFOEX; + +typedef BOOL (*MONITORENUMPROC)(HMONITOR,HDC,LPRECT,LPARAM); + +#endif //_WDL_SWELL_H_TYPES_DEFINED_ diff --git a/FEATURES.md b/FEATURES.md new file mode 100644 index 0000000..7cf759d --- /dev/null +++ b/FEATURES.md @@ -0,0 +1,194 @@ +# Just a Sample v1.3 + +This page acts as a tutorial for Just a Sample, and also documents some hidden features and use-cases. + +JAS is shorthand for Just a Sample. + +#### Other Versions +[v1.2](https://github.com/BOBONA/Just-a-Sample/blob/09ee1e0f0beeb051fab375c3d3457dd9c964145d/FEATURES.md) + +### Contents + +- [Plugin Interface](#plugin-interface) + - [Playback Controls](#playback-controls) + - [Editor and Navigator](#editor-and-navigator) + - [Effects](#effects) + - [Footer](#footer) +- [Other Features](#other-features) + +- [Tips and Tricks](#tips-and-tricks) + +## Plugin Interface + +![Plugin UI 1.2](Assets/Features/Plugin%20UI%20v1.2.png) + +### Playback Controls +![Playback Controls](Assets/Features/Playback%20Controls.png) + +After loading a sound into Just a Sample, the header is the first place to find key playback controls. + +Drag vertically over controls to change their values smoothly. You can also double-click on text to edit with precision. + +Most controls can be automated smoothly. + +1. Tune the pitch of your sound with the **semitone control** (-18 to +18) and finetune with the **cent control** (-100 to +100). + +2. **EXPERIMENTAL**: This control opens a dialog to select a region of your sample. JAS will analyze the pitch of the selected region and automatically set the semitone and cent controls, such that the center A plays at 440hz. This works best on simple sound sources. + +

+ +3. The **attack envelope** controls the volume of your instrument when a note is first activated. You have fine control over the attack length and shape. You should use a short attack time for quicker sounds and a longer attack time for more drawn out sounds. The default value is 1ms, but most sounds will use >20ms for a more natural feel. + +4. The **release envelope** controls the volume of your instrument when a note is released. The envelope will trigger automatically when your note nears the end of the sample. + +5. In Basic playback, **Lo-Fi** disables anti-aliasing, resulting in a grittier, retro sound. + +6. JAS supports two modes of playback. + + - In **Basic playback**, sounds are resampled to different pitches by changing their speed. This maintains the quality of the sound but makes it difficult to deal with notes far from center. + - In **Bungee playback**, sounds are resampled with a complex algorithm to preserve time. This takes very high CPU, but expands the range of functional notes and gives more control over timing. Distortion becomes more noticeable 2-3 octaves from center. Note that resampling to a higher pitch takes more CPU. + +7. In Bungee playback, change **playback speed** freely (0.01x to 5x) without affecting pitch. + +8. **Looping** wraps the end of a sound back to its start, allowing you to hold out a note indefinitely. When looping is enabled, JAS allows for separate control of a **sample start** and **sample end** portion. Playback occurs as follows. + + - Begin playback at sample start. + - At loop end, wrap back around to loop start. + - When the note is released, jump to the start of the sample end portion, and fully continue playback until sample end. + + JAS uses a power-preserving crossfade to handle these transitions. The crossfade length can be controlled via plugin parameter *Crossfade Samples*. + +9. **Mono** mixes the plugin output to mono, averaging the channels. This is reflected visually in the waveform views. + +10. The **gain control** scales the plugin's output volume. + +### Editor and Navigator +![Editor and Navigator](Assets/Features/Editor%20and%20Navigator.png) + +Through the **editor** and **navigator** views (top and bottom waveform), JAS enables modern, powerful navigation. Enjoy beautiful, fast rendering. These views also display active voices. + +The editor allows you to visualize your waveform and adjust the sample playback bounds. Use your mousewheel or trackpad to intuitively zoom in/out and move the editor view around the loaded sample. Modifier keys adjust the response. + +11. Freely move **sample bounds**. + +12. Freely move **loop bounds**. + +JAS enables sample-level precision. Seamlessly zoom in to the level of individual samples. + +At a high zoom level, JAS will display channels separately. For visual clarity, only a single channel will have full opacity. This is purely visual, and you can select which channel is focused by clicking on the waveform. + +

+ +JAS includes a *special* feature when the sample bounds go below a small threshold. **Waveform Mode** loops your sample bounds like a wavetable synth. Combined with the effects chain, this feature turns JAS into a unique and surprisinly versatile synthesizer. It also comes with separate tuning parameters. I have lots of fun with this, exploring the sound of different waveforms. + +Note that the **Waveform Mode** label also functions as a toggle, in case you need to disable it. + +

+ +13. JAS pairs the editor with the **navigator**, which acts as a "scrollbar" of sorts for your waveform. Use your mouse to quickly move around and resize the view. The navigator also reacts to scroll gestures like the editor. Double-click to reset the view. + +14. The **file selector** stores a history of loaded files and lets you load a sample directly from your file explorer. + +15. For convenience, JAS automatically stores small samples in plugin state. That means no more dealing with missing files! Your sampler presets will work forever, even if you lose track of your samples. You may disable this functionality with the **file link** toggle. This feature is also disabled for larger files. + +16. The **play** button lets you listen to your work without leaving the plugin. It doubles as a **halt** button whenever voices are active. + +17. For convenience, JAS allows direct **recording** into the plugin. Configure your audio inputs with the little mic icon. + +

+ +18. **Fit** the editor view to your sample bounds. + +19. **Pin** the sample bounds to the editor view. This control fixes the sample bounds to their current locations on screen, maintaining their place as you move or zoom the bounds. This is convenient when you want to easily explore a large sample without moving your sample bounds manually. + +### Effects + +![Effects](Assets/Features/Effects.png) + +The **effects** chain lets you fine-tune your sound without leaving the plugin interface. JAS provides four effect modules, which can be freely reordered. + +20. **Mix** between the unprocessed and processed sound (input and output respectively). + +21. **Enable** an effect before you start using it, or quickly disable it. + +22. **Distortion** can add subtle warmth, heavy grit, or completely warp your sound. **Density** controls the intensity of the changes. **Highpass** removes lower tones from your sound. + +23. **Chorus** simulates the sound of multiple voices playing in unison, creating a thicker, more spacious sound. **Rate** controls the speed of movement. **Depth** sets the intensity of pitch variation. **Delay** determines the base delay time. **Feedback** feeds a portion of the processed signal back into the input. Higher feedback levels produce unexpected sounds. + +24. **Reverb** adds space and echo, simulating different environments. Control the virtual **size** and **damping** of your space. **Delay** the effects of the reverb to create an echo effect. Control the **lows** and **highs** of the reflections to simulate different surfaces. + +25. The **equalizer** allows you to modify the tone of your sound by adjusting different frequency bands. You can boost or cut the **lows**, **mids**, and **highs** of your sound to shape the overall character—whether you're removing muddiness, adding presence, or brightening the top end. Change the cutoffs by moving the vertical bars with your mouse. + +### Footer + +![Footer](Assets/Features/Footer.png) + +The footer contains some additional, non-essential plugin controls. + +26. **Show** or **hide** the FX chain. JAS hides the effect chain by default for better visual clarity. + +27. **Pre-FX** applies the FX chain before the attack and release envelopes. This can be useful in mimicking the effect of "bouncing" your effects. + +28. The **help text** is context aware, providing basic info wherever your mouse is located. This includes the values of controls. + +29. **Dark mode** can reduce eye strain. + +

+ +30. **More help** opens this page :\) + +31. The **logo** shows the plugin's version number in the help text and opens the main GitHub page. + + +## Other Features +Some useful features are not visualized in the UI. + +- JAS has some extra parameters. Every DAW supports accessing parameters a bit differently, either by turning off the plugin GUI or through a parameter automation menu. + + - *Wide Tuning* is a continuous tuning control that spans -48 to +48 semitones (8 octaves). This is useful for automating pitch changes. + + - *Pitch Wheel Range* controls the radius of pitch wheel modulation in semitones. The default is 1 semitone. + + - *Disable Velocity* makes it so that all notes are played at the same volume. + + - *Voice Count* allows you to change the maximum number of voices (notes playing at once). This is set to 256 by default but can be lowered to handle CPU limitations. + + - *MIDI Range Start* and *MIDI Range End* controls the interval of notes that JAS accepts. This is useful if you want to insert multiple plugin instances on the same track to handle different intervals. + + - *MIDI Root Note* sets the note that plays the sample at its original pitch. The default is MIDI note 69 (A4). + + - *Follow MIDI Pitch* can be disabled to always play the sample at a fixed pitch. + + - *Play Until End* can be enabled to ignore note-off events, always playing the sample until end. + + - *A4 Frequency* allows you to change the reference pitch for tuning. The default is 440hz. This is only relevant for **detect pitch** and **Waveform Mode**. + + - *Crossfade Samples* controls the amount of crossfade applied when looping. + + - *Octave Speed Factor* stretches out the usable range of Bungee mode by changing the playback speed. This is somewhat like a hybrid control between Basic and Bungee. + +- JAS has **MTS-ESP** support for microtonal tuning. + +- Drag the bottom right corner to freely **resize** the plugin. + +- JAS has special support for Reaper! + + - The *UI Update* parameter triggers Reaper to save plugin state on non-parameter changes, allowing you to undo/redo every interaction. + + - On Windows, load a file in JAS using ReaScript: + ```lua + reaper.GetSetMediaTrackInfo_String(track, "P_EXT:FILE", filePath, true) + ``` + This feature is not inherently limited to Windows, but I've had a hard time getting the SDK working on other platforms. + +## Tips and Tricks + +- In **Bungee** mode, lower **playback speed** to a value less than 0.1x, and add some effects. This can create amazing pads! + +- Zoom in to **Waveform Mode** and **pin** your sample bounds to easily explore your waveform. Move your bounds around with a touchpad as you play to see how the sound morphs. + +- Set **Voice Count** to 1 to play JAS like a mono synth. + +- Turn on **Pre-FX** and enable **reverb**. Then increase **size** and decrease **damping** to maximize the effects of the reverb. Notice how **Pre-FX** smoothly cuts off the reverb when you release notes. + +- Try to have fun! Your tools are only as good as your creative potential. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c8f2e74 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Binyamin Friedman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f43ea51 --- /dev/null +++ b/Makefile @@ -0,0 +1,70 @@ +PLUGIN_NAME := Just a Sample +BUILD_DIR := out/build/macos +CMAKE_FLAGS := -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_OSX_ARCHITECTURES="arm64" \ + -DCMAKE_OSX_DEPLOYMENT_TARGET="10.13" \ + -DJAS_ENABLE_AVX2=OFF + +NPROC := $(shell sysctl -n hw.ncpu 2>/dev/null || echo 4) + +VST3_SRC := $(BUILD_DIR)/JustASample_artefacts/Release/VST3/$(PLUGIN_NAME).vst3 +AU_SRC := $(BUILD_DIR)/JustASample_artefacts/Release/AU/$(PLUGIN_NAME).component + +VST3_DST := $(HOME)/Library/Audio/Plug-Ins/VST3/$(PLUGIN_NAME).vst3 +AU_DST := $(HOME)/Library/Audio/Plug-Ins/Components/$(PLUGIN_NAME).component + +# ---------- phony targets ---------- +.PHONY: all install uninstall clean help configure build build-au build-all + +all: install + +configure: $(BUILD_DIR)/Makefile + +$(BUILD_DIR)/Makefile: + cmake -B $(BUILD_DIR) $(CMAKE_FLAGS) + +build: configure + cmake --build $(BUILD_DIR) --target JustASample_VST3 -j$(NPROC) + +build-au: configure + cmake --build $(BUILD_DIR) --target JustASample_AU -j$(NPROC) + +build-all: configure + cmake --build $(BUILD_DIR) --target JustASample_All -j$(NPROC) + +install: install-vst3 install-au + +install-vst3: build + @mkdir -p "$(VST3_DST)" + rm -rf "$(VST3_DST)" + cp -R "$(VST3_SRC)" "$(VST3_DST)" + @echo "Installed VST3 to $(VST3_DST)" + +install-au: build-au + @mkdir -p "$(AU_DST)" + rm -rf "$(AU_DST)" + cp -R "$(AU_SRC)" "$(AU_DST)" + @echo "Installed AU to $(AU_DST)" + +install-all: build-all install-vst3 install-au + +uninstall: + rm -rf "$(VST3_DST)" "$(AU_DST)" + @echo "Removed plugin from ~/Library/Audio/Plug-Ins/" + +clean: + rm -rf $(BUILD_DIR) + +help: + @echo "Targets:" + @echo " make - build and install VST3 + AU (default)" + @echo " make build - build VST3 only" + @echo " make build-au - build AU only" + @echo " make build-all - build all plugin formats" + @echo " make install - install VST3 + AU" + @echo " make install-vst3 - install VST3 only" + @echo " make install-au - install AU only" + @echo " make install-all - install all formats" + @echo " make uninstall - remove all installed copies" + @echo " make clean - remove build directory" + @echo " make help - show this message" diff --git a/Patches/apply_patch_if_needed.cmake b/Patches/apply_patch_if_needed.cmake new file mode 100644 index 0000000..95c4e47 --- /dev/null +++ b/Patches/apply_patch_if_needed.cmake @@ -0,0 +1,37 @@ +if(NOT DEFINED REPO_DIR OR NOT DEFINED PATCH_FILE OR NOT DEFINED NAME) + message(FATAL_ERROR "Missing variables: REPO_DIR PATCH_FILE NAME required") +endif() + +execute_process( + COMMAND git apply --reverse --check --ignore-whitespace -p0 "${PATCH_FILE}" + WORKING_DIRECTORY "${REPO_DIR}" + RESULT_VARIABLE reverse_result + OUTPUT_QUIET ERROR_QUIET +) + +if(reverse_result EQUAL 0) + message(STATUS "${NAME}: patch already applied") + return() +endif() + +execute_process( + COMMAND git apply --check --ignore-whitespace -p0 "${PATCH_FILE}" + WORKING_DIRECTORY "${REPO_DIR}" + RESULT_VARIABLE forward_result + OUTPUT_QUIET ERROR_QUIET +) + +if(forward_result EQUAL 0) + message(STATUS "${NAME}: applying patch") + execute_process( + COMMAND git apply --ignore-whitespace -p0 "${PATCH_FILE}" + WORKING_DIRECTORY "${REPO_DIR}" + RESULT_VARIABLE apply_result + ) + + if(NOT apply_result EQUAL 0) + message(FATAL_ERROR "${NAME}: patch failed while applying") + endif() +else() + message(FATAL_ERROR "${NAME}: patch cannot be applied (source differs)") +endif() \ No newline at end of file diff --git a/Patches/bungee_lower_cmake_and_set_num_octaves.patch b/Patches/bungee_lower_cmake_and_set_num_octaves.patch new file mode 100644 index 0000000..91040bb --- /dev/null +++ b/Patches/bungee_lower_cmake_and_set_num_octaves.patch @@ -0,0 +1,18 @@ +--- CMakeLists.txt ++++ CMakeLists.txt +@@ -11,2 +11,2 @@ +-cmake_minimum_required(VERSION 3.30...3.31) ++cmake_minimum_required(VERSION 3.22...3.31) + include(CheckCXXCompilerFlag) + +--- src/Timing.cpp ++++ src/Timing.cpp +@@ -19,7 +19,7 @@ + } + + namespace { +-static constexpr auto maxPitchOctaves = 2; ++static constexpr auto maxPitchOctaves = BUNGEE_MAX_OCTAVES; + } + + int Timing::maxInputFrameCount(bool mayDownsampleInput) const diff --git a/Patches/leaf_more_selective_imports.patch b/Patches/leaf_more_selective_imports.patch new file mode 100644 index 0000000..4861ded --- /dev/null +++ b/Patches/leaf_more_selective_imports.patch @@ -0,0 +1,98 @@ +--- leaf/leaf-config.h ++++ leaf/leaf-config.h +@@ -19,7 +19,7 @@ + //============================================================================== + + //! Include FIR tables required to use tOversampler and tWaveTableS which uses tOversampler. +-#define LEAF_INCLUDE_OVERSAMPLER_TABLES 1 ++#define LEAF_INCLUDE_OVERSAMPLER_TABLES 0 + + // Unused + #define LEAF_INCLUDE_SHAPER_TABLE 0 +@@ -28,7 +28,7 @@ + #define LEAF_INCLUDE_MTOF_TABLE 0 + + //! Include table required to use tEfficientSVF. +-#define LEAF_INCLUDE_FILTERTAN_TABLE 1 ++#define LEAF_INCLUDE_FILTERTAN_TABLE 0 + + // Unused + #define LEAF_INCLUDE_TANH_TABLE 0 +@@ -37,22 +37,22 @@ + #define LEAF_INCLUDE_ADC_TABLE 0 + + //! Include tables required to use tEnvelope and tADSR (but not tADSRT and tADSRS). +-#define LEAF_INCLUDE_ADSR_TABLES 1 ++#define LEAF_INCLUDE_ADSR_TABLES 0 + + //! Include wave table required to use tCycle. +-#define LEAF_INCLUDE_SINE_TABLE 1 ++#define LEAF_INCLUDE_SINE_TABLE 0 + + //! Include wave table required to use tTriangle. +-#define LEAF_INCLUDE_TRIANGLE_TABLE 1 ++#define LEAF_INCLUDE_TRIANGLE_TABLE 0 + + //! Include wave table required to use tSquare. +-#define LEAF_INCLUDE_SQUARE_TABLE 1 ++#define LEAF_INCLUDE_SQUARE_TABLE 0 + + //! Include wave table required to use tSawtooth. +-#define LEAF_INCLUDE_SAWTOOTH_TABLE 1 ++#define LEAF_INCLUDE_SAWTOOTH_TABLE 0 + + //! Include tables for minblep insertion, required for all tMB objects. +-#define LEAF_INCLUDE_MINBLEP_TABLES 1 ++#define LEAF_INCLUDE_MINBLEP_TABLES 0 + + #define LEAF_NO_DENORMAL_CHECK 0 + +--- leaf/leaf.h ++++ leaf/leaf.h +@@ -34,46 +34,12 @@ + #if _WIN32 || _WIN64 + + #include ".\Inc\leaf-global.h" + #include ".\Inc\leaf-math.h" +-#include ".\Inc\leaf-mempool.h" +-#include ".\Inc\leaf-tables.h" +-#include ".\Inc\leaf-distortion.h" +-#include ".\Inc\leaf-oscillators.h" +-#include ".\Inc\leaf-filters.h" +-#include ".\Inc\leaf-delay.h" +-#include ".\Inc\leaf-reverb.h" +-#include ".\Inc\leaf-effects.h" +-#include ".\Inc\leaf-envelopes.h" +-#include ".\Inc\leaf-dynamics.h" +-#include ".\Inc\leaf-analysis.h" +-#include ".\Inc\leaf-instruments.h" +-#include ".\Inc\leaf-midi.h" +-#include ".\Inc\leaf-sampling.h" +-#include ".\Inc\leaf-physical.h" +-#include ".\Inc\leaf-electrical.h" +-#include ".\Inc\leaf-vocal.h" + + #else + + #include "./Inc/leaf-global.h" + #include "./Inc/leaf-math.h" +-#include "./Inc/leaf-mempool.h" +-#include "./Inc/leaf-tables.h" +-#include "./Inc/leaf-distortion.h" +-#include "./Inc/leaf-dynamics.h" +-#include "./Inc/leaf-oscillators.h" +-#include "./Inc/leaf-filters.h" +-#include "./Inc/leaf-delay.h" +-#include "./Inc/leaf-reverb.h" +-#include "./Inc/leaf-effects.h" +-#include "./Inc/leaf-envelopes.h" +-#include "./Inc/leaf-analysis.h" +-#include "./Inc/leaf-instruments.h" +-#include "./Inc/leaf-midi.h" +-#include "./Inc/leaf-sampling.h" +-#include "./Inc/leaf-physical.h" +-#include "./Inc/leaf-electrical.h" +-#include "./Inc/leaf-vocal.h" + + #endif + diff --git a/README.md b/README.md new file mode 100644 index 0000000..f052f2f --- /dev/null +++ b/README.md @@ -0,0 +1,116 @@ +

View of full plugin

+ +# Just a Sample + +> **Note:** This is a fork of [Just a Sample](https://github.com/BOBONA/Just-a-Sample) by Binyamin Friedman. + +[Your favorite sampler shouldn't be complicated.](https://bobona.github.io/just-a-sample/) + +[Promo video](https://www.youtube.com/watch?v=P7dgOe_frXw) + +[See releases](https://github.com/BOBONA/Just-a-Sample/releases) + +Available for Windows, Mac, and Linux in VST3/AU. + +## Overview +Just a Sample is a powerful, _modern_ audio sampler, with a focus on simplicity and ease of use. +This is my personal fork of the project. All glory to Binyamin, not me. + +## Features + +[Detailed feature list](FEATURES.md) + +#### Core features +- Smoothly zoom in to the level of individual samples to +set bounds as accurately as you need. Waveform drawing is **optimized** for large samples. +- Integrated [Bungee time stretcher](https://github.com/kupix/bungee) allows for +freely modulating time and pitch independently. Try extreme slow-downs (0.01x) for unique sounds. +- Modern navigation controls allow for easy browsing of waveforms with touchpad or mouse. +- A routable FX chain includes reverb, chorus, distortion, and EQ for easy sound design. +- Waveform Mode activates when small bounds are set, looping the waveform periodically like a wavetable synth. This turns Just a Sample into a unique tone generator. +- Easily modify attack and release curves. +- Includes equal power cross-fade looping with separate attack and release sample portions. +- Supports pitch bend and fine-tuning. + +#### Extras +- Disable antialiasing for a gritty LoFi effect. +- Record samples directly into the sampler. +- Store samples directly in plugin state to remove dependencies (can be disabled). +- Auto-tune to A440 (experimental, works best on simple sounds). + +## Installation +### Pre-built + +You can download installers from, +- [itch.io](https://binyaminf.itch.io/just-a-sample) +- [GitHub Releases](https://github.com/BOBONA/Just-a-Sample/releases) + +### Build from source +Just a Sample is easy to build from source. + +```bash +# Clone the repository +git clone --recurse-submodules https://github.com/BOBONA/Just-a-Sample/.git + +# Go to the project root +cd Just-a-Sample + +# Configure the plugin (replace with windows, mac, or linux) +cmake --preset= + +# Build the plugin +cmake --build --preset=release- +``` + +Note that the first time you configure the project, CMake will download JUCE and other dependencies, which may take a while. + +Your built plugin will be located in `out/build///JustASample_artefacts///` where `` is windows, mac, or linux, `` is either debug or release, and `` is either VST3 or AU (Mac only). + +#### Build Options + +You can set the following CMake options by passing `-D